From 7e38602a15bb5d430cc3d5efcc91ef76d3a58a2e Mon Sep 17 00:00:00 2001
From: Yassir Elley <yelley@redhat.com>
Date: Tue, 22 Jul 2014 14:19:35 -0400
Subject: [PATCH] AD-GPO: add ad_gpo_cache_timeout option

---
 src/config/SSSDConfig/__init__.py.in   |   1 +
 src/config/etc/sssd.api.d/sssd-ad.conf |   1 +
 src/db/sysdb.h                         |  14 +-
 src/db/sysdb_gpo.c                     |  46 +++++-
 src/man/sssd-ad.5.xml                  |  16 ++
 src/providers/ad/ad_access.h           |   1 +
 src/providers/ad/ad_common.h           |   1 +
 src/providers/ad/ad_gpo.c              | 281 +++++++++++++++++++++------------
 src/providers/ad/ad_init.c             |   6 +
 src/providers/ad/ad_opts.h             |   1 +
 10 files changed, 260 insertions(+), 108 deletions(-)

diff --git a/src/config/SSSDConfig/__init__.py.in b/src/config/SSSDConfig/__init__.py.in
index 91f94b524b8185be96d701271443ee7a9ab67ee4..c3482b3a4df6d0fd39b2fa93c19ee5316dde0d94 100644
--- a/src/config/SSSDConfig/__init__.py.in
+++ b/src/config/SSSDConfig/__init__.py.in
@@ -169,6 +169,7 @@ option_strings = {
     'ad_access_filter' : _('LDAP filter to determine access privileges'),
     'ad_enable_gc' : _('Whether to use the Global Catalog for lookups'),
     'ad_gpo_access_control' : _('Operation mode for GPO-based access control'),
+    'ad_gpo_cache_timeout' : _("The amount of time between lookups of the GPO policy files against the AD server"),
 
     # [provider/krb5]
     'krb5_kdcip' : _('Kerberos server address'),
diff --git a/src/config/etc/sssd.api.d/sssd-ad.conf b/src/config/etc/sssd.api.d/sssd-ad.conf
index 7055c41be9ba3616459fa6683d310fa24c073a18..93d869c67f96afbea2e39d061daf578602b2f9a9 100644
--- a/src/config/etc/sssd.api.d/sssd-ad.conf
+++ b/src/config/etc/sssd.api.d/sssd-ad.conf
@@ -7,6 +7,7 @@ ad_enable_dns_sites = bool, None, false
 ad_access_filter = str, None, false
 ad_enable_gc = bool, None, false
 ad_gpo_access_control = str, None, false
+ad_gpo_cache_timeout = int, None, false
 ldap_uri = str, None, false
 ldap_backup_uri = str, None, false
 ldap_search_base = str, None, false
diff --git a/src/db/sysdb.h b/src/db/sysdb.h
index 63f596007cdf53c5ebbdccf37f3c4bcc7830a205..addf13196352825009a4a4a0ebaed7e343c72a14 100644
--- a/src/db/sysdb.h
+++ b/src/db/sysdb.h
@@ -873,6 +873,7 @@ errno_t sysdb_search_object_by_sid(TALLOC_CTX *mem_ctx,
 #define SYSDB_GPO_FILTER "(&(objectClass="SYSDB_GPO_OC")("SYSDB_GPO_GUID_ATTR"=%s))"
 #define SYSDB_GPO_GUID_ATTR "gpoGUID"
 #define SYSDB_GPO_VERSION_ATTR "gpoVersion"
+#define SYSDB_GPO_TIMEOUT_ATTR "gpoPolicyFileTimeout"
 
 #define SYSDB_TMPL_GPO_BASE SYSDB_GPO_CONTAINER","SYSDB_DOM_BASE
 #define SYSDB_TMPL_GPO SYSDB_GPO_GUID_ATTR"=%s,"SYSDB_TMPL_GPO_BASE
@@ -881,15 +882,18 @@ errno_t sysdb_search_object_by_sid(TALLOC_CTX *mem_ctx,
         SYSDB_NAME, \
         SYSDB_GPO_GUID_ATTR, \
         SYSDB_GPO_VERSION_ATTR, \
+        SYSDB_GPO_TIMEOUT_ATTR, \
         NULL }
 
 errno_t sysdb_gpo_store_gpo(struct sss_domain_info *domain,
                             const char *gpo_guid,
-                            int gpo_version);
+                            int gpo_version,
+                            int cache_timeout,
+                            time_t now);
 
-errno_t sysdb_gpo_get_gpo(TALLOC_CTX *mem_ctx,
-                          struct sss_domain_info *domain,
-                          const char *gpo_guid,
-                          struct ldb_result **_result);
+errno_t sysdb_gpo_get_gpo_by_guid(TALLOC_CTX *mem_ctx,
+                                  struct sss_domain_info *domain,
+                                  const char *gpo_guid,
+                                  struct ldb_result **_result);
 
 #endif /* __SYS_DB_H__ */
diff --git a/src/db/sysdb_gpo.c b/src/db/sysdb_gpo.c
index 3c23c5b8fae6aaa41168b1466f4b3e520231e7e2..228f131ace761909c98e0c02fffc889599f86c07 100644
--- a/src/db/sysdb_gpo.c
+++ b/src/db/sysdb_gpo.c
@@ -49,7 +49,9 @@ sysdb_gpo_dn(TALLOC_CTX *mem_ctx, struct sss_domain_info *domain,
 errno_t
 sysdb_gpo_store_gpo(struct sss_domain_info *domain,
                     const char *gpo_guid,
-                    int gpo_version)
+                    int gpo_version,
+                    int cache_timeout,
+                    time_t now)
 {
     errno_t ret, sret;
     int lret;
@@ -81,6 +83,10 @@ sysdb_gpo_store_gpo(struct sss_domain_info *domain,
         goto done;
     }
 
+    if (!now) {
+        now = time(NULL);
+    }
+
     in_transaction = true;
 
     /* Check for an existing gpo_guid entry */
@@ -140,6 +146,21 @@ sysdb_gpo_store_gpo(struct sss_domain_info *domain,
             goto done;
         }
 
+        /* Add the Policy File Timeout */
+        lret = ldb_msg_add_empty(update_msg, SYSDB_GPO_TIMEOUT_ATTR,
+                                 LDB_FLAG_MOD_ADD, NULL);
+        if (lret != LDB_SUCCESS) {
+            ret = sysdb_error_to_errno(lret);
+            goto done;
+        }
+
+        lret = ldb_msg_add_fmt(update_msg, SYSDB_GPO_TIMEOUT_ATTR, "%lu",
+                               ((cache_timeout) ? (now + cache_timeout) : 0));
+        if (lret != LDB_SUCCESS) {
+            ret = sysdb_error_to_errno(lret);
+            goto done;
+        }
+
         lret = ldb_add(domain->sysdb->ldb, update_msg);
         if (lret != LDB_SUCCESS) {
             DEBUG(SSSDBG_MINOR_FAILURE,
@@ -170,6 +191,21 @@ sysdb_gpo_store_gpo(struct sss_domain_info *domain,
             goto done;
         }
 
+        /* Add the Policy File Timeout */
+        lret = ldb_msg_add_empty(update_msg, SYSDB_GPO_TIMEOUT_ATTR,
+                                 LDB_FLAG_MOD_REPLACE, NULL);
+        if (lret != LDB_SUCCESS) {
+            ret = sysdb_error_to_errno(lret);
+            goto done;
+        }
+
+        lret = ldb_msg_add_fmt(update_msg, SYSDB_GPO_TIMEOUT_ATTR, "%lu",
+                               ((cache_timeout) ? (now + cache_timeout) : 0));
+        if (lret != LDB_SUCCESS) {
+            ret = sysdb_error_to_errno(lret);
+            goto done;
+        }
+
         lret = ldb_modify(domain->sysdb->ldb, update_msg);
         if (lret != LDB_SUCCESS) {
             DEBUG(SSSDBG_MINOR_FAILURE,
@@ -202,10 +238,10 @@ done:
 }
 
 errno_t
-sysdb_gpo_get_gpo(TALLOC_CTX *mem_ctx,
-                  struct sss_domain_info *domain,
-                  const char *gpo_guid,
-                  struct ldb_result **_result)
+sysdb_gpo_get_gpo_by_guid(TALLOC_CTX *mem_ctx,
+                          struct sss_domain_info *domain,
+                          const char *gpo_guid,
+                          struct ldb_result **_result)
 {
     errno_t ret;
     int lret;
diff --git a/src/man/sssd-ad.5.xml b/src/man/sssd-ad.5.xml
index d8c423541311922bb6d6eeec32df475a930fd1ba..8d90bc13359b025833b4badde50ec811241fe623 100644
--- a/src/man/sssd-ad.5.xml
+++ b/src/man/sssd-ad.5.xml
@@ -317,6 +317,22 @@ FOREST:EXAMPLE.COM:(memberOf=cn=admins,ou=groups,dc=example,dc=com)
                 </varlistentry>
 
                 <varlistentry>
+                    <term>ad_gpo_cache_timeout (integer)</term>
+                    <listitem>
+                        <para>
+                            The amount of time between lookups of GPO policy
+                            files against the AD server. This will reduce the
+                            latency and load on the AD server if there are
+                            many access-control requests made in a short
+                            period.
+                        </para>
+                        <para>
+                            Default: 5 (seconds)
+                        </para>
+                    </listitem>
+                </varlistentry>
+
+                <varlistentry>
                     <term>dyndns_update (boolean)</term>
                     <listitem>
                         <para>
diff --git a/src/providers/ad/ad_access.h b/src/providers/ad/ad_access.h
index 4a9bd40a09472844fe9fa42f7453366264e627fd..60ff5db66c9663e736338cdf7694eb75584c3674 100644
--- a/src/providers/ad/ad_access.h
+++ b/src/providers/ad/ad_access.h
@@ -33,6 +33,7 @@ struct ad_access_ctx {
         GPO_ACCESS_CONTROL_PERMISSIVE,
         GPO_ACCESS_CONTROL_ENFORCING
     } gpo_access_control_mode;
+    int gpo_cache_timeout;
 };
 
 void
diff --git a/src/providers/ad/ad_common.h b/src/providers/ad/ad_common.h
index b2949c37fe68add7c44c25258aed45ce6d68c836..c3dc6c1a7362a312262d130ef6188f169bf9b832 100644
--- a/src/providers/ad/ad_common.h
+++ b/src/providers/ad/ad_common.h
@@ -51,6 +51,7 @@ enum ad_basic_opt {
     AD_ACCESS_FILTER,
     AD_ENABLE_GC,
     AD_GPO_ACCESS_CONTROL,
+    AD_GPO_CACHE_TIMEOUT,
 
     AD_OPTS_BASIC /* opts counter */
 };
diff --git a/src/providers/ad/ad_gpo.c b/src/providers/ad/ad_gpo.c
index a650fa13867e40bf8275a646885e229fa44bc57b..c26011d672886faadaf2bb60c4537c2de8bf6532 100644
--- a/src/providers/ad/ad_gpo.c
+++ b/src/providers/ad/ad_gpo.c
@@ -123,6 +123,7 @@ struct gp_gpo {
     int num_gpo_cse_guids;
     int gpo_func_version;
     int gpo_flags;
+    bool send_to_child;
 };
 
 enum ace_eval_status {
@@ -157,19 +158,19 @@ int ad_gpo_process_gpo_recv(struct tevent_req *req,
                             int *num_candidate_gpos);
 struct tevent_req *ad_gpo_process_cse_send(TALLOC_CTX *mem_ctx,
                                            struct tevent_context *ev,
+                                           bool send_to_child,
                                            struct sss_domain_info *domain,
+                                           const char *gpo_guid,
                                            const char *smb_server,
                                            const char *smb_share,
                                            const char *smb_path,
                                            const char *smb_cse_suffix,
-                                           const char *gpo_guid);
+                                           int cached_gpt_version,
+                                           int gpo_timeout_option);
 int ad_gpo_process_cse_recv(struct tevent_req *req,
                             TALLOC_CTX *mem_ctx,
                             int *_sysvol_gpt_version,
-                            int *_allowed_size,
-                            char ***_allowed_sids,
-                            int *_denied_size,
-                            char ***_denied_sids);
+                            const char **_policy_filename);
 
 /* == ad_gpo_access_send/recv helpers =======================================*/
 
@@ -871,6 +872,7 @@ struct ad_gpo_access_state {
     struct sss_domain_info *domain;
     const char *user;
     enum gpo_access_control_mode gpo_mode;
+    int gpo_timeout_option;
     const char *ad_hostname;
     const char *target_dn;
     struct gp_gpo **dacl_filtered_gpos;
@@ -886,6 +888,12 @@ static void ad_gpo_process_som_done(struct tevent_req *subreq);
 static void ad_gpo_process_gpo_done(struct tevent_req *subreq);
 
 static errno_t ad_gpo_cse_step(struct tevent_req *req);
+static errno_t ad_gpo_parse_policy_file(TALLOC_CTX *mem_ctx,
+                                        const char *filename,
+                                        char ***allowed_sids,
+                                        int *allowed_size,
+                                        char ***denied_sids,
+                                        int *denied_size);
 static void ad_gpo_cse_done(struct tevent_req *subreq);
 
 struct tevent_req *
@@ -921,6 +929,7 @@ ad_gpo_access_send(TALLOC_CTX *mem_ctx,
     state->user = user;
     state->ldb_ctx = sysdb_ctx_get_ldb(domain->sysdb);
     state->gpo_mode = ctx->gpo_access_control_mode;
+    state->gpo_timeout_option = ctx->gpo_cache_timeout;
     state->ad_hostname = dp_opt_get_string(ctx->ad_options, AD_HOSTNAME);
     state->opts = ctx->sdap_access_ctx->id_ctx->opts;
     state->timeout = dp_opt_get_int(state->opts->basic, SDAP_SEARCH_TIMEOUT);
@@ -1310,6 +1319,11 @@ ad_gpo_cse_step(struct tevent_req *req)
     struct tevent_req *subreq;
     struct ad_gpo_access_state *state;
     int i = 0;
+    struct ldb_result *res;
+    errno_t ret;
+    bool send_to_child = true;
+    int cached_gpt_version = 0;
+    time_t policy_file_timeout = 0;
 
     state = tevent_req_data(req, struct ad_gpo_access_state);
 
@@ -1332,14 +1346,60 @@ ad_gpo_cse_step(struct tevent_req *req)
     DEBUG(SSSDBG_TRACE_FUNC, "smb_path: %s\n", cse_filtered_gpo->smb_path);
     DEBUG(SSSDBG_TRACE_FUNC, "gpo_guid: %s\n", cse_filtered_gpo->gpo_guid);
 
+    /* retrieve gpo cache entry; set cached_gpt_version to -1 if unavailable */
+    DEBUG(SSSDBG_TRACE_FUNC, "retrieving GPO from cache [%s]\n",
+          cse_filtered_gpo->gpo_guid);
+    ret = sysdb_gpo_get_gpo_by_guid(state,
+                                    state->domain,
+                                    cse_filtered_gpo->gpo_guid,
+                                    &res);
+    if (ret == EOK) {
+        /*
+         * Note: if the timeout is valid, then we can later avoid downloading
+         * the GPT.INI file, as well as any policy files (i.e. we don't need
+         * to interact with the gpo_child at all). However, even if the timeout
+         * is not valid, while we will have to interact with the gpo child to
+         * download the GPT.INI file, we may still be able to avoid downloading
+         * the policy files (if the cached_gpt_version is the same as the
+         * GPT.INI version). In other words, the timeout is *not* an expiration
+         * for the entire cache entry; the cached_gpt_version never expires.
+         */
+
+        cached_gpt_version = ldb_msg_find_attr_as_int(res->msgs[0],
+                                                      SYSDB_GPO_VERSION_ATTR,
+                                                      0);
+
+        policy_file_timeout = ldb_msg_find_attr_as_uint64
+            (res->msgs[0], SYSDB_GPO_TIMEOUT_ATTR, 0);
+
+        if (policy_file_timeout >= time(NULL)) {
+            send_to_child = false;
+        }
+    } else if (ret == ENOENT) {
+        DEBUG(SSSDBG_TRACE_FUNC, "ENOENT\n");
+        cached_gpt_version = -1;
+    } else {
+        DEBUG(SSSDBG_FATAL_FAILURE, "Could not read GPO from cache: [%s]\n",
+               strerror(ret));
+        return ret;
+    }
+
+    DEBUG(SSSDBG_TRACE_FUNC, "send_to_child: %d\n", send_to_child);
+    DEBUG(SSSDBG_TRACE_FUNC, "cached_gpt_version: %d\n", cached_gpt_version);
+
+    cse_filtered_gpo->send_to_child = send_to_child;
+
     subreq = ad_gpo_process_cse_send(state,
                                      state->ev,
+                                     send_to_child,
                                      state->domain,
+                                     cse_filtered_gpo->gpo_guid,
                                      cse_filtered_gpo->smb_server,
                                      cse_filtered_gpo->smb_share,
                                      cse_filtered_gpo->smb_path,
                                      GP_EXT_GUID_SECURITY_SUFFIX,
-                                     cse_filtered_gpo->gpo_guid);
+                                     cached_gpt_version,
+                                     state->gpo_timeout_option);
 
     tevent_req_set_callback(subreq, ad_gpo_cse_done, req);
     return EAGAIN;
@@ -1361,6 +1421,7 @@ ad_gpo_cse_done(struct tevent_req *subreq)
     struct ad_gpo_access_state *state;
     int ret;
     int sysvol_gpt_version;
+    const char *policy_filename = NULL;
     char **allowed_sids;
     int allowed_size;
     char **denied_sids;
@@ -1377,8 +1438,7 @@ ad_gpo_cse_done(struct tevent_req *subreq)
     DEBUG(SSSDBG_TRACE_FUNC, "gpo_guid: %s\n", gpo_guid);
 
     ret = ad_gpo_process_cse_recv(subreq, state, &sysvol_gpt_version,
-                                  &allowed_size, &allowed_sids,
-                                  &denied_size, &denied_sids);
+                                  &policy_filename);
 
     talloc_zfree(subreq);
 
@@ -1388,17 +1448,20 @@ ad_gpo_cse_done(struct tevent_req *subreq)
         goto done;
     }
 
-    DEBUG(SSSDBG_TRACE_FUNC, "sysvol_gpt_version: %d\n", sysvol_gpt_version);
-
-    ret = sysdb_gpo_store_gpo(state->domain, gpo_guid, sysvol_gpt_version);
+    ret = ad_gpo_parse_policy_file(state,
+                                   policy_filename,
+                                   &allowed_sids,
+                                   &allowed_size,
+                                   &denied_sids,
+                                   &denied_size);
 
     if (ret != EOK) {
-        DEBUG(SSSDBG_OP_FAILURE, "Unable to store gpo cache entry: [%d](%s}\n",
-              ret, sss_strerror(ret));
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Cannot parse policy file: [%s][%d][%s]\n",
+              policy_filename, ret, strerror(ret));
         goto done;
     }
 
-    /* TBD: allowed/denied_sids/size, should be retrieved from cache */
     ret = ad_gpo_access_check
         (state, state->gpo_mode, state->user, state->domain,
          allowed_sids, allowed_size, denied_sids, denied_size);
@@ -2387,8 +2450,22 @@ ad_gpo_extract_smb_components(TALLOC_CTX *mem_ctx,
     *_smb_server = talloc_asprintf(mem_ctx, "%s%s",
                                    SMB_STANDARD_URI,
                                    server_hostname);
+    if (*_smb_server == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
     *_smb_share = talloc_asprintf(mem_ctx, "/%s", smb_share);
+    if (*_smb_share == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
     *_smb_path = talloc_asprintf(mem_ctx, "/%s", smb_path);
+    if (*_smb_path == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
 
     ret = EOK;
 
@@ -3141,8 +3218,13 @@ ad_gpo_parse_gpo_child_response(TALLOC_CTX *mem_ctx,
 
 struct ad_gpo_process_cse_state {
     struct tevent_context *ev;
+    struct sss_domain_info *domain;
+    int gpo_timeout_option;
+    const char *gpo_guid;
     const char *smb_path;
     const char *smb_cse_suffix;
+    int sysvol_gpt_version;
+    const char *policy_filename;
     pid_t child_pid;
     uint8_t *buf;
     ssize_t len;
@@ -3198,19 +3280,20 @@ static void gpo_cse_done(struct tevent_req *subreq);
 struct tevent_req *
 ad_gpo_process_cse_send(TALLOC_CTX *mem_ctx,
                         struct tevent_context *ev,
+                        bool send_to_child,
                         struct sss_domain_info *domain,
+                        const char *gpo_guid,
                         const char *smb_server,
                         const char *smb_share,
                         const char *smb_path,
                         const char *smb_cse_suffix,
-                        const char *gpo_guid)
+                        int cached_gpt_version,
+                        int gpo_timeout_option)
 {
     struct tevent_req *req;
     struct tevent_req *subreq;
     struct ad_gpo_process_cse_state *state;
-    struct ldb_result *res;
     struct io_buffer *buf = NULL;
-    int cached_gpt_version = 0;
     errno_t ret;
 
     req = tevent_req_create(mem_ctx, &state, struct ad_gpo_process_cse_state);
@@ -3219,73 +3302,82 @@ ad_gpo_process_cse_send(TALLOC_CTX *mem_ctx,
         return NULL;
     }
 
+    state->sysvol_gpt_version = -1;
+
+    if (!send_to_child) {
+        /*
+         * if we don't need to talk to child (b/c cache timeout is valid),
+         * we simply set the policy_filename and complete the request
+         */
+        state->policy_filename =
+            talloc_asprintf(state,
+                            GPO_CACHE_PATH"/%s/Policies/%s%s",
+                            domain->name,
+                            gpo_guid,
+                            GP_EXT_GUID_SECURITY_SUFFIX);
+        if (state->policy_filename == NULL) {
+            ret = ENOMEM;
+            goto immediately;
+        }
+
+        DEBUG(SSSDBG_TRACE_FUNC, "policy_filename:%s\n", state->policy_filename);
+        ret = EOK;
+        goto immediately;
+    }
+
     state->ev = ev;
     state->buf = NULL;
     state->len = 0;
+    state->domain = domain;
+    state->gpo_timeout_option = gpo_timeout_option;
+    state->gpo_guid = gpo_guid;
     state->smb_path = smb_path;
     state->smb_cse_suffix = smb_cse_suffix;
     state->io = talloc(state, struct io);
     if (state->io == NULL) {
         DEBUG(SSSDBG_CRIT_FAILURE, "talloc failed.\n");
         ret = ENOMEM;
-        goto fail;
+        goto immediately;
     }
 
     state->io->write_to_child_fd = -1;
     state->io->read_from_child_fd = -1;
     talloc_set_destructor((void *) state->io, gpo_child_io_destructor);
 
-    /* retrieve cached gpt version (or set it to -1 if unavailable) */
-    DEBUG(SSSDBG_TRACE_FUNC, "retrieving GPO from cache [%s]\n", gpo_guid);
-    ret = sysdb_gpo_get_gpo(state, domain, gpo_guid, &res);
-    if (ret != EOK) {
-        switch (ret) {
-        case ENOENT:
-            DEBUG(SSSDBG_TRACE_FUNC, "ENOENT\n");
-            cached_gpt_version = -1;
-            break;
-        default:
-            DEBUG(SSSDBG_FATAL_FAILURE, "Could not read GPO from cache: [%s]\n",
-                  strerror(ret));
-            goto fail;
-        }
-    }
-
-    if (cached_gpt_version != -1) {
-        /* cached_gpt_version is in cache, so retrieve it from cache */
-        cached_gpt_version = ldb_msg_find_attr_as_int(res->msgs[0],
-                                                      SYSDB_GPO_VERSION_ATTR,
-                                                      0);
-    }
-    DEBUG(SSSDBG_TRACE_FUNC, "cached_gpt_version: %d\n", cached_gpt_version);
-
     /* prepare the data to pass to child */
     ret = create_cse_send_buffer(state, smb_server, smb_share, smb_path,
                                  smb_cse_suffix, cached_gpt_version, &buf);
     if (ret != EOK) {
         DEBUG(SSSDBG_CRIT_FAILURE, "create_cse_send_buffer failed.\n");
-        goto fail;
+        goto immediately;
     }
 
     ret = gpo_fork_child(req);
     if (ret != EOK) {
         DEBUG(SSSDBG_CRIT_FAILURE, "gpo_fork_child failed.\n");
-        goto fail;
+        goto immediately;
     }
 
     subreq = write_pipe_send(state, ev, buf->data, buf->size,
                              state->io->write_to_child_fd);
     if (subreq == NULL) {
         ret = ENOMEM;
-        goto fail;
+        goto immediately;
     }
     tevent_req_set_callback(subreq, gpo_cse_step, req);
 
     return req;
 
-fail:
-    tevent_req_error(req, ret);
-    tevent_req_post(req, ev);
+immediately:
+
+    if (ret == EOK) {
+        tevent_req_done(req);
+        tevent_req_post(req, ev);
+    } else {
+        tevent_req_error(req, ret);
+        tevent_req_post(req, ev);
+    }
+
     return req;
 }
 
@@ -3321,6 +3413,9 @@ static void gpo_cse_done(struct tevent_req *subreq)
 {
     struct tevent_req *req;
     struct ad_gpo_process_cse_state *state;
+    uint32_t sysvol_gpt_version = -1;
+    uint32_t child_result;
+    time_t now;
 
     req = tevent_req_callback_data(subreq, struct tevent_req);
     state = tevent_req_data(req, struct ad_gpo_process_cse_state);
@@ -3336,69 +3431,59 @@ static void gpo_cse_done(struct tevent_req *subreq)
     close(state->io->read_from_child_fd);
     state->io->read_from_child_fd = -1;
 
-    tevent_req_done(req);
-    return;
-}
-
-int ad_gpo_process_cse_recv(struct tevent_req *req,
-                            TALLOC_CTX *mem_ctx,
-                            int *_sysvol_gpt_version,
-                            int *_allowed_size,
-                            char ***_allowed_sids,
-                            int *_denied_size,
-                            char ***_denied_sids)
-{
-    int ret;
-    uint32_t sysvol_gpt_version;
-    uint32_t result;
-    char **allowed_sids;
-    int allowed_size;
-    char **denied_sids;
-    int denied_size;
-    struct ad_gpo_process_cse_state *state;
-    const char *policy_filename = NULL;
-
-    state = tevent_req_data(req, struct ad_gpo_process_cse_state);
-
-    TEVENT_REQ_RETURN_ON_ERROR(req);
-
     ret = ad_gpo_parse_gpo_child_response(state, state->buf, state->len,
-                                          &sysvol_gpt_version, &result);
+                                          &sysvol_gpt_version, &child_result);
     if (ret != EOK) {
         DEBUG(SSSDBG_CRIT_FAILURE,
               "ad_gpo_parse_gpo_child_response failed: [%d][%s]\n",
               ret, strerror(ret));
-        return ret;
-    } else if (result != 0){
+        tevent_req_error(req, ret);
+    } else if (child_result != 0){
         DEBUG(SSSDBG_CRIT_FAILURE,
-              "Error in gpo_child: [%d][%s]\n", result, strerror(result));
-        return result;
+              "Error in gpo_child: [%d][%s]\n",
+              child_result, strerror(child_result));
+        tevent_req_error(req, child_result);
     }
 
-    policy_filename = talloc_asprintf(mem_ctx, GPO_CACHE_PATH"%s%s",
-                               state->smb_path, state->smb_cse_suffix);
-
-    DEBUG(SSSDBG_TRACE_FUNC, "policy_filename:%s\n", policy_filename);
+    state->sysvol_gpt_version = sysvol_gpt_version;
+    state->policy_filename = talloc_asprintf(state,
+                                             GPO_CACHE_PATH"%s%s",
+                                             state->smb_path,
+                                             state->smb_cse_suffix);
+    if (state->policy_filename == NULL) {
+        tevent_req_error(req, ENOMEM);
+    }
 
-    ret = ad_gpo_parse_policy_file(state,
-                                   policy_filename,
-                                   &allowed_sids,
-                                   &allowed_size,
-                                   &denied_sids,
-                                   &denied_size);
+    DEBUG(SSSDBG_TRACE_FUNC, "policy_filename:%s\n", state->policy_filename);
 
+    now = time(NULL);
+    DEBUG(SSSDBG_TRACE_FUNC, "sysvol_gpt_version: %d\n", sysvol_gpt_version);
+    ret = sysdb_gpo_store_gpo(state->domain, state->gpo_guid, sysvol_gpt_version,
+                              state->gpo_timeout_option, now);
     if (ret != EOK) {
-        DEBUG(SSSDBG_CRIT_FAILURE,
-              "Cannot parse policy file: [%s][%d][%s]\n",
-              policy_filename, ret, strerror(ret));
-        return ret;
+        DEBUG(SSSDBG_OP_FAILURE, "Unable to store gpo cache entry: [%d](%s}\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ret);
+        return;
     }
 
-    *_sysvol_gpt_version = sysvol_gpt_version;
-    *_allowed_size = allowed_size;
-    *_allowed_sids = talloc_steal(mem_ctx, allowed_sids);
-    *_denied_size = denied_size;
-    *_denied_sids = talloc_steal(mem_ctx, denied_sids);
+    tevent_req_done(req);
+    return;
+}
+
+int ad_gpo_process_cse_recv(struct tevent_req *req,
+                            TALLOC_CTX *mem_ctx,
+                            int *_sysvol_gpt_version,
+                            const char **_policy_filename)
+{
+    struct ad_gpo_process_cse_state *state;
+
+    state = tevent_req_data(req, struct ad_gpo_process_cse_state);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    *_sysvol_gpt_version = state->sysvol_gpt_version;
+    *_policy_filename = talloc_steal(mem_ctx, state->policy_filename);
 
     return EOK;
 }
diff --git a/src/providers/ad/ad_init.c b/src/providers/ad/ad_init.c
index 0a54d39709a61b39f74e2db04665c0911ab320ed..da3f6c4247f4599908c846d7be4e14b5c0e3d44d 100644
--- a/src/providers/ad/ad_init.c
+++ b/src/providers/ad/ad_init.c
@@ -371,6 +371,7 @@ sssm_ad_access_init(struct be_ctx *bectx,
     struct ad_id_ctx *ad_id_ctx;
     const char *filter;
     const char *gpo_access_control_mode;
+    int gpo_cache_timeout;
 
     access_ctx = talloc_zero(bectx, struct ad_access_ctx);
     if (!access_ctx) return ENOMEM;
@@ -439,6 +440,11 @@ sssm_ad_access_init(struct be_ctx *bectx,
         goto fail;
     }
 
+    /* GPO cache timeout */
+    gpo_cache_timeout =
+        dp_opt_get_int(access_ctx->ad_options, AD_GPO_CACHE_TIMEOUT);
+    access_ctx->gpo_cache_timeout = gpo_cache_timeout;
+
     *ops = &ad_access_ops;
     *pvt_data = access_ctx;
 
diff --git a/src/providers/ad/ad_opts.h b/src/providers/ad/ad_opts.h
index 1f49f9c62247ae1cbed6ec5ec4c52ddb406ee1b6..a3ade012a7c1efb705a011697af36c28e39b1a9b 100644
--- a/src/providers/ad/ad_opts.h
+++ b/src/providers/ad/ad_opts.h
@@ -39,6 +39,7 @@ struct dp_option ad_basic_opts[] = {
     { "ad_access_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING},
     { "ad_enable_gc", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE },
     { "ad_gpo_access_control", DP_OPT_STRING, { "permissive" }, NULL_STRING },
+    { "ad_gpo_cache_timeout", DP_OPT_NUMBER, { .number = 5 }, NULL_NUMBER },
     DP_OPTION_TERMINATOR
 };
 
-- 
1.8.5.3

