From 16dd4955983aee00ed8bb4a6e8a02814827ce0a6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pavel=20B=C5=99ezina?= <pbrezina@redhat.com>
Date: Mon, 19 Dec 2011 15:46:17 +0100
Subject: [PATCH 4/4] SUDO Integration - periodical update of rules in data
 provider

https://fedorahosted.org/sssd/ticket/1110

Adds new configuration options:
 - ldap_sudo_refresh_enabled - enable/disable periodical updates
 - ldap_sudo_refresh_timeout - rules timeout (refresh period)
---
 Makefile.am                              |    2 +
 src/config/SSSDConfig.py                 |    2 +
 src/config/etc/sssd.api.d/sssd-ldap.conf |    2 +
 src/providers/ipa/ipa_common.c           |    2 +
 src/providers/ipa/ipa_common.h           |    2 +-
 src/providers/ldap/ldap_common.c         |   60 ++++++++
 src/providers/ldap/ldap_common.h         |    1 +
 src/providers/ldap/ldap_init.c           |    5 +
 src/providers/ldap/sdap.h                |    2 +
 src/providers/ldap/sdap_sudo_timer.c     |  236 ++++++++++++++++++++++++++++++
 src/providers/ldap/sdap_sudo_timer.h     |   41 +++++
 11 files changed, 354 insertions(+), 1 deletions(-)
 create mode 100644 src/providers/ldap/sdap_sudo_timer.c
 create mode 100644 src/providers/ldap/sdap_sudo_timer.h

diff --git a/Makefile.am b/Makefile.am
index a0855ae..f8082ed 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -340,6 +340,7 @@ dist_noinst_HEADERS = \
     src/providers/ldap/sdap_async.h \
     src/providers/ldap/sdap_async_private.h \
     src/providers/ldap/sdap_sudo_cache.h \
+    src/providers/ldap/sdap_sudo_timer.h \
     src/providers/ldap/sdap_sudo.h \
     src/providers/ldap/sdap_id_op.h \
     src/providers/ipa/ipa_common.h \
@@ -913,6 +914,7 @@ libsss_ldap_la_LDFLAGS = \
     -module
 if BUILD_SUDO
 libsss_ldap_la_SOURCES += src/providers/ldap/sdap_sudo_cache.c \
+                 src/providers/ldap/sdap_sudo_timer.c \
                  src/providers/ldap/sdap_sudo.c
 endif
 
diff --git a/src/config/SSSDConfig.py b/src/config/SSSDConfig.py
index 0a73893..c5c73b1 100644
--- a/src/config/SSSDConfig.py
+++ b/src/config/SSSDConfig.py
@@ -240,6 +240,8 @@ option_strings = {
 
     # [provider/ldap/sudo]
     'ldap_sudo_search_base' : _('Base DN for sudo rules lookups'),
+    'ldap_sudo_refresh_enabled' : _('Enable periodical update of all sudo rules'),
+    'ldap_sudo_refresh_timeout' : _('Length of time between rules updates'),
     'ldap_sudorule_object_class' : _('Object class for sudo rules'),
     'ldap_sudorule_name' : _('Sudo rule name'),
     'ldap_sudorule_command' : _('Sudo rule command attribute'),
diff --git a/src/config/etc/sssd.api.d/sssd-ldap.conf b/src/config/etc/sssd.api.d/sssd-ldap.conf
index b155c2b..fb48dcd 100644
--- a/src/config/etc/sssd.api.d/sssd-ldap.conf
+++ b/src/config/etc/sssd.api.d/sssd-ldap.conf
@@ -109,6 +109,8 @@ ldap_chpass_dns_service_name = str, None, false
 
 [provider/ldap/sudo]
 ldap_sudo_search_base = str, None, false
+ldap_sudo_refresh_enabled = bool, None, false
+ldap_sudo_refresh_timeout = int, None, false
 ldap_sudorule_object_class = str, None, false
 ldap_sudorule_name = str, None, false
 ldap_sudorule_command = str, None, false
diff --git a/src/providers/ipa/ipa_common.c b/src/providers/ipa/ipa_common.c
index c3ea8c3..12c69dd 100644
--- a/src/providers/ipa/ipa_common.c
+++ b/src/providers/ipa/ipa_common.c
@@ -61,6 +61,8 @@ struct dp_option ipa_def_ldap_opts[] = {
     { "ldap_group_search_scope", DP_OPT_STRING, { "sub" }, NULL_STRING },
     { "ldap_group_search_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING },
     { "ldap_sudo_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ldap_sudo_refresh_enabled", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
+    { "ldap_sudo_refresh_timeout", DP_OPT_NUMBER, { .number = 300 }, NULL_NUMBER },
     { "ldap_schema", DP_OPT_STRING, { "ipa_v1" }, NULL_STRING },
     { "ldap_offline_timeout", DP_OPT_NUMBER, { .number = 60 }, NULL_NUMBER },
     { "ldap_force_upper_case_realm", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE },
diff --git a/src/providers/ipa/ipa_common.h b/src/providers/ipa/ipa_common.h
index 577e3e1..f29a238 100644
--- a/src/providers/ipa/ipa_common.h
+++ b/src/providers/ipa/ipa_common.h
@@ -35,7 +35,7 @@ struct ipa_service {
 /* the following defines are used to keep track of the options in the ldap
  * module, so that if they change and ipa is not updated correspondingly
  * this will trigger a runtime abort error */
-#define IPA_OPTS_BASIC_TEST 56
+#define IPA_OPTS_BASIC_TEST 58
 
 /* the following define is used to keep track of the options in the krb5
  * module, so that if they change and ipa is not updated correspondingly
diff --git a/src/providers/ldap/ldap_common.c b/src/providers/ldap/ldap_common.c
index 6ca6f34..18df5ba 100644
--- a/src/providers/ldap/ldap_common.c
+++ b/src/providers/ldap/ldap_common.c
@@ -26,6 +26,7 @@
 #include "providers/fail_over.h"
 #include "providers/ldap/sdap_async_private.h"
 #include "providers/krb5/krb5_common.h"
+#include "providers/ldap/sdap_sudo_timer.h"
 #include "db/sysdb_sudo.h"
 
 #include "util/sss_krb5.h"
@@ -51,6 +52,8 @@ struct dp_option default_basic_opts[] = {
     { "ldap_group_search_scope", DP_OPT_STRING, { "sub" }, NULL_STRING },
     { "ldap_group_search_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING },
     { "ldap_sudo_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ldap_sudo_refresh_enabled", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
+    { "ldap_sudo_refresh_timeout", DP_OPT_NUMBER, { .number = 300 }, NULL_NUMBER },
     { "ldap_schema", DP_OPT_STRING, { "rfc2307" }, NULL_STRING },
     { "ldap_offline_timeout", DP_OPT_NUMBER, { .number = 60 }, NULL_NUMBER },
     { "ldap_force_upper_case_realm", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
@@ -564,6 +567,63 @@ int ldap_get_sudo_options(TALLOC_CTX *memctx,
     return EOK;
 }
 
+int sdap_sudo_setup_tasks(struct sdap_id_ctx *id_ctx)
+{
+    struct sdap_sudo_refresh_ctx *refresh_ctx = NULL;
+    struct timeval tv;
+    int ret = EOK;
+    bool refreshed = false;
+    bool refresh_enabled = dp_opt_get_bool(id_ctx->opts->basic,
+                                           SDAP_SUDO_REFRESH_ENABLED);
+
+    /* set up periodical update of sudo rules */
+    if (refresh_enabled) {
+        refresh_ctx = sdap_sudo_refresh_ctx_init(id_ctx, id_ctx->be, id_ctx,
+                                                 id_ctx->opts,
+                                                 tevent_timeval_zero());
+        if (refresh_ctx == NULL) {
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  ("sdap_sudo_refresh_ctx_init() failed!\n"));
+            return ENOMEM;
+        }
+
+        /* If this is the first startup, we need to kick off
+         * an refresh immediately, to close a window where
+         * clients requesting sudo information won't get an
+         * immediate reply with no entries
+         */
+        ret = sysdb_sudo_get_refreshed(id_ctx->be->sysdb, &refreshed);
+        if (ret != EOK) {
+            return ret;
+        }
+        if (refreshed) {
+            /* At least one update has previously run,
+             * so clients will get cached data. We will delay
+             * starting to enumerate by 10s so we don't slow
+             * down the startup process if this is happening
+             * during system boot.
+             */
+            tv = tevent_timeval_current_ofs(10, 0);
+            DEBUG(SSSDBG_FUNC_DATA, ("Delaying first refresh of SUDO rules "
+                  "for 10 seconds\n"));
+        } else {
+            /* This is our first startup. Schedule the
+             * update to start immediately once we
+             * enter the mainloop.
+             */
+            tv = tevent_timeval_current();
+        }
+
+        ret = sdap_sudo_refresh_set_timer(refresh_ctx, tv);
+        if (ret != EOK) {
+            talloc_free(refresh_ctx);
+            return ret;
+        }
+    }
+
+    return EOK;
+}
+
 errno_t sdap_parse_search_base(TALLOC_CTX *mem_ctx,
                                struct dp_option *opts, int class,
                                struct sdap_search_base ***_search_bases)
diff --git a/src/providers/ldap/ldap_common.h b/src/providers/ldap/ldap_common.h
index e6bb8b6..0ab0a5e 100644
--- a/src/providers/ldap/ldap_common.h
+++ b/src/providers/ldap/ldap_common.h
@@ -88,6 +88,7 @@ void sdap_pam_access_handler(struct be_req *breq);
 #ifdef BUILD_SUDO
 /* sudo */
 void sdap_sudo_handler(struct be_req *breq);
+int sdap_sudo_setup_tasks(struct sdap_id_ctx *ctx);
 #endif
 
 
diff --git a/src/providers/ldap/ldap_init.c b/src/providers/ldap/ldap_init.c
index 8c4d3e6..2721b00 100644
--- a/src/providers/ldap/ldap_init.c
+++ b/src/providers/ldap/ldap_init.c
@@ -419,6 +419,11 @@ int sssm_ldap_sudo_init(struct be_ctx *be_ctx,
         return ret;
     }
 
+    ret = sdap_sudo_setup_tasks(id_ctx);
+    if (ret != EOK) {
+        return ret;
+    }
+
     return ret;
 #else
     return EOK;
diff --git a/src/providers/ldap/sdap.h b/src/providers/ldap/sdap.h
index da328d2..5d6bfb8 100644
--- a/src/providers/ldap/sdap.h
+++ b/src/providers/ldap/sdap.h
@@ -165,6 +165,8 @@ enum sdap_basic_opt {
     SDAP_GROUP_SEARCH_SCOPE,
     SDAP_GROUP_SEARCH_FILTER,
     SDAP_SUDO_SEARCH_BASE,
+    SDAP_SUDO_REFRESH_ENABLED,
+    SDAP_SUDO_REFRESH_TIMEOUT,
     SDAP_SCHEMA,
     SDAP_OFFLINE_TIMEOUT,
     SDAP_FORCE_UPPER_CASE_REALM,
diff --git a/src/providers/ldap/sdap_sudo_timer.c b/src/providers/ldap/sdap_sudo_timer.c
new file mode 100644
index 0000000..cc664ab
--- /dev/null
+++ b/src/providers/ldap/sdap_sudo_timer.c
@@ -0,0 +1,236 @@
+/*
+    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 <errno.h>
+#include <tevent.h>
+
+#include "util/util.h"
+#include "providers/ldap/ldap_common.h"
+#include "providers/ldap/sdap_sudo_timer.h"
+#include "providers/ldap/sdap_sudo.h"
+#include "db/sysdb_sudo.h"
+
+struct sdap_sudo_refresh_ctx {
+    struct be_ctx *be_ctx;
+    struct sdap_id_ctx *id_ctx;
+    struct sdap_options *opts;
+
+    struct timeval last_refresh;
+};
+
+static void sdap_sudo_refresh_timer(struct tevent_context *ev,
+                                   struct tevent_timer *tt,
+                                   struct timeval tv, void *pvt);
+
+static void sdap_sudo_refresh_reschedule(struct tevent_req *req);
+
+static void sdap_sudo_refresh_timeout(struct tevent_context *ev,
+                                      struct tevent_timer *te,
+                                      struct timeval tv, void *pvt);
+
+struct sdap_sudo_refresh_ctx *
+sdap_sudo_refresh_ctx_init(TALLOC_CTX *mem_ctx,
+                           struct be_ctx *be_ctx,
+                           struct sdap_id_ctx *id_ctx,
+                           struct sdap_options *opts,
+                           struct timeval last_refresh)
+{
+    struct sdap_sudo_refresh_ctx *refresh_ctx = NULL;
+
+    refresh_ctx = talloc_zero(mem_ctx, struct sdap_sudo_refresh_ctx);
+    if (refresh_ctx == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, ("talloc_zero() failed!\n"));
+        return NULL;
+    }
+
+    refresh_ctx->be_ctx = be_ctx;
+    refresh_ctx->id_ctx = id_ctx;
+    refresh_ctx->opts = opts;
+    refresh_ctx->last_refresh = last_refresh;
+
+    return refresh_ctx;
+}
+
+int sdap_sudo_refresh_set_timer(struct sdap_sudo_refresh_ctx *ctx,
+                                struct timeval tv)
+{
+    struct tevent_timer *enum_task;
+
+    DEBUG(SSSDBG_TRACE_FUNC, ("Scheduling next refresh of SUDO rules at "
+          "%ld.%ld\n", (long)tv.tv_sec, (long)tv.tv_usec));
+
+    enum_task = tevent_add_timer(ctx->be_ctx->ev, ctx,
+                                 tv, sdap_sudo_refresh_timer, ctx);
+    if (!enum_task) {
+        DEBUG(SSSDBG_FATAL_FAILURE,
+              ("FATAL: failed to setup SUDO rules refresh task!\n"));
+        return EFAULT;
+    }
+
+    return EOK;
+}
+
+static void sdap_sudo_refresh_timer(struct tevent_context *ev,
+                                    struct tevent_timer *tt,
+                                    struct timeval tv, void *pvt)
+{
+    struct sdap_sudo_refresh_ctx *refresh_ctx = NULL;
+    struct sdap_sudo_ctx *sudo_ctx = NULL;
+    struct tevent_timer *timeout = NULL;
+    struct tevent_req *req = NULL;
+    int delay = 0;
+    int ret;
+
+    refresh_ctx = talloc_get_type(pvt, struct sdap_sudo_refresh_ctx);
+
+    delay = dp_opt_get_int(refresh_ctx->opts->basic, SDAP_SUDO_REFRESH_TIMEOUT);
+
+    if (be_is_offline(refresh_ctx->be_ctx)) {
+        DEBUG(SSSDBG_TRACE_FUNC, ("Backend is marked offline, retry later!\n"));
+        tv = tevent_timeval_current_ofs(delay, 0);
+        ret = sdap_sudo_refresh_set_timer(refresh_ctx, tv);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE, ("Error setting up SUDO refresh timer\n"));
+        }
+        return;
+    }
+
+    /* create sudo context */
+    sudo_ctx = talloc_zero(refresh_ctx, struct sdap_sudo_ctx);
+    if (sudo_ctx == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, ("talloc_zero() failed!\n"));
+        tv = tevent_timeval_current_ofs(delay, 0);
+        ret = sdap_sudo_refresh_set_timer(refresh_ctx, tv);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE, ("Error setting up SUDO refresh timer\n"));
+        }
+
+        return;
+    }
+
+    sudo_ctx->be_ctx = refresh_ctx->be_ctx;
+    sudo_ctx->be_req = NULL;
+    sudo_ctx->sdap_ctx = refresh_ctx->id_ctx;
+    sudo_ctx->sdap_op = NULL;
+    sudo_ctx->sdap_conn_cache = refresh_ctx->id_ctx->conn_cache;
+    sudo_ctx->username = NULL; /* download all rules */
+    sudo_ctx->uid = 0;
+    sudo_ctx->groups = NULL;
+
+    /* send request */
+    req = sdap_sudo_refresh_send(sudo_ctx, sudo_ctx);
+    if (req == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, ("Failed to schedule refresh of SUDO rules, "
+              "retrying later!\n"));
+        tv = tevent_timeval_current_ofs(delay, 0);
+        ret = sdap_sudo_refresh_set_timer(refresh_ctx, tv);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE, ("Error setting up SUDO refresh timer\n"));
+        }
+
+        return;
+    }
+    refresh_ctx->last_refresh = tevent_timeval_current();
+
+    tevent_req_set_callback(req, sdap_sudo_refresh_reschedule, refresh_ctx);
+
+    /* schedule timeout */
+    tv = tevent_timeval_current_ofs(delay, 0);
+    timeout = tevent_add_timer(refresh_ctx->be_ctx->ev, req, tv,
+                               sdap_sudo_refresh_timeout, req);
+    if (timeout == NULL) {
+        /* If we can't guarantee a timeout, we
+         * need to cancel the request, to avoid
+         * the possibility of starting another
+         * concurrently
+         */
+        talloc_zfree(req);
+
+        DEBUG(SSSDBG_CRIT_FAILURE, ("Failed to schedule refresh of SUDO rules, "
+              "retrying later!\n"));
+        tv = tevent_timeval_current_ofs(delay, 0);
+        ret = sdap_sudo_refresh_set_timer(refresh_ctx, tv);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE, ("Error setting up SUDO refresh timer\n"));
+        }
+    }
+
+    return;
+}
+
+static void sdap_sudo_refresh_reschedule(struct tevent_req *req)
+{
+    struct sdap_sudo_refresh_ctx *refresh_ctx = NULL;
+    struct sdap_sudo_ctx *sudo_ctx = NULL;
+    struct timeval tv;
+    int delay;
+    int dp_error;
+    int error;
+    int ret;
+
+    refresh_ctx = tevent_req_callback_data(req, struct sdap_sudo_refresh_ctx);
+    ret = sdap_sudo_refresh_recv(req, &sudo_ctx, &dp_error, &error);
+    talloc_zfree(req);
+    talloc_zfree(sudo_ctx);
+    if (ret != EOK) {
+        tv = tevent_timeval_current();
+    } else {
+        tv = refresh_ctx->last_refresh;
+
+        /* Ok, we've completed a refresh. Save this to the
+         * sysdb so we can postpone starting up the refresh
+         * process on the next SSSD service restart (to avoid
+         * slowing down system boot-up
+         */
+        ret = sysdb_sudo_set_refreshed(refresh_ctx->be_ctx->sysdb, true);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  ("Could not mark domain as having refreshed.\n"));
+            /* This error is non-fatal, so continue */
+        }
+    }
+
+    delay = dp_opt_get_int(refresh_ctx->opts->basic, SDAP_SUDO_REFRESH_TIMEOUT);
+    tv = tevent_timeval_add(&tv, delay, 0);
+    ret = sdap_sudo_refresh_set_timer(refresh_ctx, tv);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, ("Error setting up SUDO refresh timer\n"));
+    }
+}
+
+static void sdap_sudo_refresh_timeout(struct tevent_context *ev,
+                                      struct tevent_timer *te,
+                                      struct timeval tv, void *pvt)
+{
+    struct tevent_req *req = talloc_get_type(pvt, struct tevent_req);
+    struct sdap_sudo_refresh_ctx *refresh_ctx = NULL;
+    int delay;
+
+    refresh_ctx = tevent_req_callback_data(req, struct sdap_sudo_refresh_ctx);
+
+    delay = dp_opt_get_int(refresh_ctx->opts->basic, SDAP_SUDO_REFRESH_TIMEOUT);
+    DEBUG(SSSDBG_CRIT_FAILURE, ("Refreshing SUDO rules timed out!"
+          " Timeout too small? (%ds)!\n", delay));
+
+    tv = tevent_timeval_current_ofs(delay, 0);
+    sdap_sudo_refresh_set_timer(refresh_ctx, tv);
+
+    talloc_zfree(req);
+}
diff --git a/src/providers/ldap/sdap_sudo_timer.h b/src/providers/ldap/sdap_sudo_timer.h
new file mode 100644
index 0000000..ad7dcad
--- /dev/null
+++ b/src/providers/ldap/sdap_sudo_timer.h
@@ -0,0 +1,41 @@
+/*
+    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/>.
+*/
+
+#ifndef _SDAP_SUDO_TIMER_H_
+#define _SDAP_SUDO_TIMER_H_
+
+#include <time.h>
+
+#include "providers/dp_backend.h"
+#include "providers/ldap/ldap_common.h"
+
+struct sdap_sudo_refresh_ctx;
+
+int sdap_sudo_refresh_set_timer(struct sdap_sudo_refresh_ctx *ctx,
+                                struct timeval tv);
+
+struct sdap_sudo_refresh_ctx *
+sdap_sudo_refresh_ctx_init(TALLOC_CTX *mem_ctx,
+                           struct be_ctx *be_ctx,
+                           struct sdap_id_ctx *id_ctx,
+                           struct sdap_options *opts,
+                           struct timeval last_refresh);
+
+#endif /* _SDAP_SUDO_TIMER_H_ */
-- 
1.7.6.4
