From f8bf169bec7c7f98f38d7530685bc62b7cf44f2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20B=C5=99ezina?= Date: Tue, 24 Jan 2012 13:42:59 +0100 Subject: [PATCH] SUDO Integration - in-memory cache in responder New sudo responder option: cache_timeout https://fedorahosted.org/sssd/ticket/1111 --- Makefile.am | 1 + src/confdb/confdb.h | 2 + src/responder/sudo/sudosrv.c | 21 +++ src/responder/sudo/sudosrv_cache.c | 224 ++++++++++++++++++++++++++++ src/responder/sudo/sudosrv_cmd.c | 46 +++++- src/responder/sudo/sudosrv_get_sudorules.c | 17 ++- src/responder/sudo/sudosrv_private.h | 27 ++++ 7 files changed, 330 insertions(+), 8 deletions(-) create mode 100644 src/responder/sudo/sudosrv_cache.c diff --git a/Makefile.am b/Makefile.am index 2e47e96..4a3374f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -468,6 +468,7 @@ sssd_sudo_SOURCES = \ src/responder/sudo/sudosrv_get_sudorules.c \ src/responder/sudo/sudosrv_query.c \ src/responder/sudo/sudosrv_dp.c \ + src/responder/sudo/sudosrv_cache.c \ $(SSSD_RESPONDER_OBJ) sssd_sudo_LDADD = \ $(SSSD_LIBS) \ diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h index 7cfc73d..83da447 100644 --- a/src/confdb/confdb.h +++ b/src/confdb/confdb.h @@ -93,6 +93,8 @@ /* SUDO */ #define CONFDB_SUDO_CONF_ENTRY "config/sudo" +#define CONFDB_SUDO_CACHE_TIMEOUT "sudo_cache_timeout" +#define CONFDB_DEFAULT_SUDO_CACHE_TIMEOUT 180 /* Data Provider */ #define CONFDB_DP_CONF_ENTRY "config/dp" diff --git a/src/responder/sudo/sudosrv.c b/src/responder/sudo/sudosrv.c index 841be43..d61533f 100644 --- a/src/responder/sudo/sudosrv.c +++ b/src/responder/sudo/sudosrv.c @@ -129,6 +129,27 @@ int sudo_process_init(TALLOC_CTX *mem_ctx, sudo_dp_reconnect_init, iter); } + /* Get responder options */ + + /* Get cache_timeout option */ + ret = confdb_get_int(sudo_ctx->rctx->cdb, sudo_ctx, + CONFDB_SUDO_CONF_ENTRY, CONFDB_SUDO_CACHE_TIMEOUT, + CONFDB_DEFAULT_SUDO_CACHE_TIMEOUT, + &sudo_ctx->cache_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, ("Error reading from confdb (%d) [%s]\n", + ret, strerror(ret))); + return ret; + } + + /* Initialize in-memory cache */ + ret = sss_hash_create(sudo_ctx, 10, &sudo_ctx->cache); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + ("Could not create hash table: [%s]", strerror(ret))); + return ret; + } + DEBUG(SSSDBG_TRACE_FUNC, ("SUDO Initialization complete\n")); return EOK; diff --git a/src/responder/sudo/sudosrv_cache.c b/src/responder/sudo/sudosrv_cache.c new file mode 100644 index 0000000..50d48ef --- /dev/null +++ b/src/responder/sudo/sudosrv_cache.c @@ -0,0 +1,224 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2011 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 . +*/ + +#include +#include +#include + +#include "util/util.h" +#include "confdb/confdb.h" +#include "db/sysdb.h" +#include "responder/sudo/sudosrv_private.h" + +struct sudo_cache_entry { + time_t expire; + size_t num_rules; + struct sysdb_attrs **rules; +}; + +static hash_key_t *sudosrv_cache_create_key(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *username) +{ + hash_key_t *key = talloc_zero(NULL, hash_key_t); + if (key == NULL) { + return NULL; + } + + key->type = HASH_KEY_STRING; + if (username == NULL) { + key->str = talloc_strdup(key, domain->name); + } else { + key->str = talloc_asprintf(key, "%s:%s", domain->name, username); + } + + if (key->str == NULL) { + talloc_free(key); + return NULL; + } + + return talloc_steal(mem_ctx, key); +} + +errno_t sudosrv_cache_set_entry(hash_table_t *table, + struct sss_domain_info *domain, + const char *username, + size_t num_rules, + struct sysdb_attrs **rules, + time_t timeout) +{ + struct sudo_cache_entry *cache_entry = NULL; + hash_key_t *key = NULL; + hash_value_t value; + TALLOC_CTX *tmp_ctx = NULL; + errno_t ret; + int hret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + goto done; + } + + /* create key */ + key = sudosrv_cache_create_key(tmp_ctx, domain, username); + if (key == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, ("Unable to create hash key.\n")); + ret = ENOMEM; + goto done; + } + + /* create value */ + cache_entry = talloc_zero(tmp_ctx, struct sudo_cache_entry); + if (cache_entry == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, ("Unable to create hash value.\n")); + ret = ENOMEM; + goto done; + } + cache_entry->expire = time(NULL) + timeout; + cache_entry->num_rules = num_rules; + cache_entry->rules = talloc_steal(cache_entry, rules); + + value.type = HASH_VALUE_PTR; + value.ptr = cache_entry; + + /* insert value */ + hret = hash_enter(table, key, &value); + if (hret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + ("Unable to add [%s] to SUDO cache", key->str)); + DEBUG(SSSDBG_TRACE_LIBS, + ("Hash error [%d][%s]", hret, hash_error_string(hret))); + ret = EIO; + goto done; + } + + talloc_steal(table, cache_entry); + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t sudosrv_cache_lookup_internal(hash_table_t *table, + struct sss_domain_info *domain, + const char *username, + size_t *num_rules, + struct sysdb_attrs ***rules) +{ + struct sudo_cache_entry *cache_entry = NULL; + hash_key_t *key = NULL; + hash_value_t value; + TALLOC_CTX *tmp_ctx = NULL; + errno_t ret; + int hret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + goto done; + } + + /* create key */ + key = sudosrv_cache_create_key(tmp_ctx, domain, username); + if (key == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, ("Unable to create hash key.\n")); + ret = ENOMEM; + goto done; + } + + hret = hash_lookup(table, key, &value); + if (hret == HASH_SUCCESS) { + /* cache hit */ + cache_entry = value.ptr; + + /* check expiration time */ + if (cache_entry->expire < time(NULL)) { + /* delete entry */ + hret = hash_delete(table, key); + if (hret != HASH_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, + ("Could not clear [%s] from SUDO cache.\n", key->str)); + DEBUG(SSSDBG_TRACE_LIBS, + ("Hash error [%d][%s]", hret, hash_error_string(hret))); + } else { + talloc_free(cache_entry); + DEBUG(SSSDBG_TRACE_INTERNAL, + ("[%s] removed from SUDO cache\n", key->str)); + } + + /* expired */ + ret = EAGAIN; + } else { + *num_rules = cache_entry->num_rules; + *rules = cache_entry->rules; + ret = EOK; + } + } else if(hret == HASH_ERROR_KEY_NOT_FOUND) { + /* cache miss */ + ret = ENOENT; + } else { + /* error */ + ret = EIO; + } + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t sudosrv_cache_lookup(hash_table_t *table, + struct sudo_dom_ctx *dctx, + bool check_next, + const char *username, + size_t *num_rules, + struct sysdb_attrs ***rules) +{ + struct sss_domain_info *domain = dctx->domain; + errno_t ret; + + if (!check_next) { + return sudosrv_cache_lookup_internal(table, dctx->domain, username, + num_rules, rules); + } + + while (domain != NULL) { + if (domain->fqnames) { + domain = domain->next; + continue; + } + + ret = sudosrv_cache_lookup_internal(table, domain, username, + num_rules, rules); + if (ret == EOK || EAGAIN) { + /* user is in this domain */ + dctx->domain = domain; + return ret; + } else if (ret != ENOENT) { + /* error */ + return ret; + } + + /* user is not in this domain cache, check next */ + domain = domain->next; + } + + /* user is not in cache */ + return ENOENT; +} diff --git a/src/responder/sudo/sudosrv_cmd.c b/src/responder/sudo/sudosrv_cmd.c index 3550e8b..9cda526 100644 --- a/src/responder/sudo/sudosrv_cmd.c +++ b/src/responder/sudo/sudosrv_cmd.c @@ -151,6 +151,15 @@ static int sudosrv_cmd_get_sudorules(struct cli_ctx *cli_ctx) cmd_ctx->cli_ctx = cli_ctx; cmd_ctx->type = SSS_DP_SUDO_USER; + /* get responder ctx */ + cmd_ctx->sudo_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct sudo_ctx); + if (!cmd_ctx->sudo_ctx) { + DEBUG(SSSDBG_FATAL_FAILURE, ("sudo_ctx not set\n")); + ret = EFAULT; + goto done; + } + + /* create domain ctx */ dctx = talloc_zero(cmd_ctx, struct sudo_dom_ctx); if (!dctx) { ret = ENOMEM; @@ -201,8 +210,18 @@ static int sudosrv_cmd_get_sudorules(struct cli_ctx *cli_ctx) cmd_ctx->check_next = true; } - /* ok, find it ! */ - ret = sudosrv_get_sudorules(dctx); + /* try to find rules in in-memory cache */ + ret = sudosrv_cache_lookup(cmd_ctx->sudo_ctx->cache, dctx, + cmd_ctx->check_next, cmd_ctx->username, + &dctx->res_count, &dctx->res); + if (ret == EOK) { + /* cache hit */ + DEBUG(SSSDBG_FUNC_DATA, ("Returning rules for [%s@%s] " + "from im-memory cache\n", cmd_ctx->username, dctx->domain->name)); + } else if (ret == EAGAIN || ret == ENOENT) { + /* cache expired or missed */ + ret = sudosrv_get_sudorules(dctx); + } /* else error */ done: return sudosrv_cmd_done(dctx, ret); @@ -224,6 +243,15 @@ static int sudosrv_cmd_get_defaults(struct cli_ctx *cli_ctx) cmd_ctx->username = NULL; cmd_ctx->check_next = false; + /* get responder ctx */ + cmd_ctx->sudo_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct sudo_ctx); + if (!cmd_ctx->sudo_ctx) { + DEBUG(SSSDBG_FATAL_FAILURE, ("sudo_ctx not set\n")); + ret = EFAULT; + goto done; + } + + /* create domain ctx */ dctx = talloc_zero(cmd_ctx, struct sudo_dom_ctx); if (!dctx) { ret = ENOMEM; @@ -246,8 +274,18 @@ static int sudosrv_cmd_get_defaults(struct cli_ctx *cli_ctx) goto done; } - /* ok, find it ! */ - ret = sudosrv_get_rules(dctx); + /* try to find rules in in-memory cache */ + ret = sudosrv_cache_lookup(cmd_ctx->sudo_ctx->cache, dctx, + cmd_ctx->check_next, cmd_ctx->username, + &dctx->res_count, &dctx->res); + if (ret == EOK) { + /* cache hit */ + DEBUG(SSSDBG_FUNC_DATA, ("Returning defaults settings for [%s] " + "from in-memory cache\n", dctx->domain->name)); + } else if (ret == EAGAIN || ret == ENOENT) { + /* cache expired or missed */ + ret = sudosrv_get_rules(dctx); + } /* else error */ done: return sudosrv_cmd_done(dctx, ret); diff --git a/src/responder/sudo/sudosrv_get_sudorules.c b/src/responder/sudo/sudosrv_get_sudorules.c index b7e1705..7915978 100644 --- a/src/responder/sudo/sudosrv_get_sudorules.c +++ b/src/responder/sudo/sudosrv_get_sudorules.c @@ -208,7 +208,6 @@ static void sudosrv_check_user_dp_callback(uint16_t err_maj, uint32_t err_min, ("Data Provider returned, check the cache again\n")); dctx->check_provider = false; ret = sudosrv_get_user(dctx); - /* FIXME - set entry into cache so that we don't perform initgroups too often */ if (ret == EAGAIN) { goto done; } else if (ret != EOK) { @@ -248,8 +247,6 @@ errno_t sudosrv_get_rules(struct sudo_dom_ctx *dctx) struct sudo_cmd_ctx *cmd_ctx = dctx->cmd_ctx; struct dp_callback_ctx *cb_ctx = NULL; - /* FIXME - cache logic will be here. For now, just refresh - * the cache unconditionally */ dpreq = sss_dp_get_sudoers_send(cmd_ctx->cli_ctx, cmd_ctx->cli_ctx->rctx, dctx->domain, false, @@ -317,7 +314,6 @@ sudosrv_get_sudorules_dp_callback(uint16_t err_maj, uint32_t err_min, "Will try to return what we have in cache\n", (unsigned int)err_maj, (unsigned int)err_min, err_msg)); - /* FIXME - cache or next domain? */ /* Loop to the next domain if possible */ if (dctx->domain->next && dctx->cmd_ctx->check_next) { dctx->domain = dctx->domain->next; @@ -353,6 +349,7 @@ static errno_t sudosrv_get_sudorules_from_cache(struct sudo_dom_ctx *dctx) errno_t ret; struct sysdb_ctx *sysdb; struct cli_ctx *cli_ctx = dctx->cmd_ctx->cli_ctx; + struct sudo_ctx *sudo_ctx = dctx->cmd_ctx->sudo_ctx; uid_t uid; char **groupnames; @@ -391,6 +388,18 @@ static errno_t sudosrv_get_sudorules_from_cache(struct sudo_dom_ctx *dctx) goto done; } + /* Store result in in-memory cache */ + ret = sudosrv_cache_set_entry(sudo_ctx->cache, dctx->domain, + dctx->cmd_ctx->username, dctx->res_count, + dctx->res, sudo_ctx->cache_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, ("Unable to store rules in cache for " + "[%s@%s]\n", dctx->cmd_ctx->username, dctx->domain->name)); + } else { + DEBUG(SSSDBG_FUNC_DATA, ("Rules for [%s@%s] stored in in-memory cache\n", + dctx->cmd_ctx->username, dctx->domain->name)); + } + DEBUG(SSSDBG_TRACE_FUNC, ("Returning rules for [%s@%s]\n", dctx->cmd_ctx->username, dctx->domain->name)); diff --git a/src/responder/sudo/sudosrv_private.h b/src/responder/sudo/sudosrv_private.h index b59aca4..60a9cfe 100644 --- a/src/responder/sudo/sudosrv_private.h +++ b/src/responder/sudo/sudosrv_private.h @@ -38,10 +38,23 @@ enum sss_dp_sudo_type { struct sudo_ctx { struct resp_ctx *rctx; + + /* + * options + */ + int cache_timeout; + + /* + * Key: domain for SSS_DP_SUDO_DEFAULTS + * domain:username for SSS_DP_SUDO_USER + * Val: struct sudo_cache_entry * + */ + hash_table_t *cache; }; struct sudo_cmd_ctx { struct cli_ctx *cli_ctx; + struct sudo_ctx *sudo_ctx; enum sss_dp_sudo_type type; char *username; bool check_next; @@ -121,4 +134,18 @@ sss_dp_get_sudoers_recv(TALLOC_CTX *mem_ctx, dbus_uint32_t *err_min, char **err_msg); +errno_t sudosrv_cache_lookup(hash_table_t *table, + struct sudo_dom_ctx *dctx, + bool check_next, + const char *username, + size_t *res_count, + struct sysdb_attrs ***res); + +errno_t sudosrv_cache_set_entry(hash_table_t *table, + struct sss_domain_info *domain, + const char *username, + size_t res_count, + struct sysdb_attrs **res, + time_t timeout); + #endif /* _SUDOSRV_PRIVATE_H_ */ -- 1.7.6.4