From f8bf169bec7c7f98f38d7530685bc62b7cf44f2f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pavel=20B=C5=99ezina?= <pbrezina@redhat.com>
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 <pbrezina@redhat.com>
+
+    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 <http://www.gnu.org/licenses/>.
+*/
+
+#include <talloc.h>
+#include <dhash.h>
+#include <time.h>
+
+#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
