From 37342752cf18ee6b7046c7c99107a6b7d6773dd7 Mon Sep 17 00:00:00 2001
From: Stephen Gallagher <sgallagh@redhat.com>
Date: Thu, 6 May 2010 10:09:41 -0400
Subject: [PATCH 2/2] Add ldap_access_filter option

This option (applicable to access_provider=ldap) allows the admin
to set an additional LDAP search filter that must match in order
for a user to be granted access to the system.

Common examples for this would be limiting access to users by in a
particular group, for example:
ldap_access_filter = memberOf=cn=access_group,ou=Groups,dc=example,dc=com
---
 src/Makefile.am                  |    2 +
 src/providers/ipa/ipa_common.c   |    3 +-
 src/providers/ipa/ipa_common.h   |    2 +-
 src/providers/ldap/ldap_common.c |    3 +-
 src/providers/ldap/ldap_common.h |    3 +
 src/providers/ldap/ldap_init.c   |   47 +++
 src/providers/ldap/sdap.h        |    1 +
 src/providers/ldap/sdap_access.c |  575 ++++++++++++++++++++++++++++++++++++++
 src/providers/ldap/sdap_access.h |   39 +++
 9 files changed, 672 insertions(+), 3 deletions(-)
 create mode 100644 src/providers/ldap/sdap_access.c
 create mode 100644 src/providers/ldap/sdap_access.h

diff --git a/src/Makefile.am b/src/Makefile.am
index 5c428e74f778d62738b43b89bac64174a5de3576..530ccfe02960f63a95ad9f2013beeac3883b66d9 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -343,6 +343,7 @@ dist_noinst_HEADERS = \
     providers/krb5/krb5_utils.h \
     providers/ldap/ldap_common.h \
     providers/ldap/sdap.h \
+    providers/ldap/sdap_access.h \
     providers/ldap/sdap_async.h \
     providers/ldap/sdap_async_private.h \
     providers/ipa/ipa_common.h \
@@ -720,6 +721,7 @@ libsss_ldap_la_SOURCES = \
     providers/ldap/ldap_id.c \
     providers/ldap/ldap_id_enum.c \
     providers/ldap/ldap_id_cleanup.c \
+    providers/ldap/sdap_access.c \
     providers/ldap/ldap_auth.c \
     providers/ldap/ldap_init.c \
     providers/ldap/ldap_common.c \
diff --git a/src/providers/ipa/ipa_common.c b/src/providers/ipa/ipa_common.c
index 4b7dfba17d216ede49bc65701c66729c756b3179..f42960fb3e990b4d902d9652b86196d718e9c46c 100644
--- a/src/providers/ipa/ipa_common.c
+++ b/src/providers/ipa/ipa_common.c
@@ -70,7 +70,8 @@ struct dp_option ipa_def_ldap_opts[] = {
     { "ldap_pwd_policy", DP_OPT_STRING, { "none" } , NULL_STRING },
     { "ldap_referrals", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE },
     { "account_cache_expiration", DP_OPT_NUMBER, { .number = 0 }, NULL_NUMBER },
-    { "ldap_dns_service_name", DP_OPT_STRING, { SSS_LDAP_SRV_NAME }, NULL_STRING }
+    { "ldap_dns_service_name", DP_OPT_STRING, { SSS_LDAP_SRV_NAME }, NULL_STRING },
+    { "ldap_access_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING }
 };
 
 struct sdap_attr_map ipa_attr_map[] = {
diff --git a/src/providers/ipa/ipa_common.h b/src/providers/ipa/ipa_common.h
index 2d8718694d7cced558cfee3d6574bd08d3ba14c8..9678e0cea60642ca9b73592f87efdf218bc1e379 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 33
+#define IPA_OPTS_BASIC_TEST 34
 
 /* 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 03b2133a870fa85fbf7c7c013b4abb47ea556c44..7b5bd11df3aca63561faa31541707a47fd311df1 100644
--- a/src/providers/ldap/ldap_common.c
+++ b/src/providers/ldap/ldap_common.c
@@ -64,7 +64,8 @@ struct dp_option default_basic_opts[] = {
     { "ldap_pwd_policy", DP_OPT_STRING, { "none" } , NULL_STRING },
     { "ldap_referrals", DP_OPT_BOOL, BOOL_TRUE, BOOL_TRUE },
     { "account_cache_expiration", DP_OPT_NUMBER, { .number = 0 }, NULL_NUMBER },
-    { "ldap_dns_service_name", DP_OPT_STRING, { SSS_LDAP_SRV_NAME }, NULL_STRING }
+    { "ldap_dns_service_name", DP_OPT_STRING, { SSS_LDAP_SRV_NAME }, NULL_STRING },
+    { "ldap_access_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING }
 };
 
 struct sdap_attr_map generic_attr_map[] = {
diff --git a/src/providers/ldap/ldap_common.h b/src/providers/ldap/ldap_common.h
index 3998e30013aaf56562043001b1af4fdde63ccbf9..2fec7e2dad53b00d814202fb9b49e70436903cb0 100644
--- a/src/providers/ldap/ldap_common.h
+++ b/src/providers/ldap/ldap_common.h
@@ -73,6 +73,9 @@ void sdap_pam_auth_handler(struct be_req *breq);
 /* chpass */
 void sdap_pam_chpass_handler(struct be_req *breq);
 
+/* access */
+void sdap_pam_access_handler(struct be_req *breq);
+
 
 
 void sdap_handler_done(struct be_req *req, int dp_err,
diff --git a/src/providers/ldap/ldap_init.c b/src/providers/ldap/ldap_init.c
index 917ece0cbc5f3fbdf3d63603cac2af3a0a16918d..639aba19585ba117a16384d186ff744c25afa65a 100644
--- a/src/providers/ldap/ldap_init.c
+++ b/src/providers/ldap/ldap_init.c
@@ -25,6 +25,7 @@
 #include "providers/child_common.h"
 #include "providers/ldap/ldap_common.h"
 #include "providers/ldap/sdap_async_private.h"
+#include "providers/ldap/sdap_access.h"
 
 static void sdap_shutdown(struct be_req *req);
 
@@ -46,6 +47,12 @@ struct bet_ops sdap_chpass_ops = {
     .finalize = sdap_shutdown
 };
 
+/* Access Handler */
+struct bet_ops sdap_access_ops = {
+    .handler = sdap_pam_access_handler,
+    .finalize = sdap_shutdown
+};
+
 int sssm_ldap_id_init(struct be_ctx *bectx,
                       struct bet_ops **ops,
                       void **pvt_data)
@@ -179,6 +186,46 @@ int sssm_ldap_chpass_init(struct be_ctx *bectx,
     return ret;
 }
 
+int sssm_ldap_access_init(struct be_ctx *bectx,
+                          struct bet_ops **ops,
+                          void **pvt_data)
+{
+    int ret;
+    struct sdap_access_ctx *access_ctx;
+
+    access_ctx = talloc_zero(bectx, struct sdap_access_ctx);
+    if(access_ctx == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    ret = sssm_ldap_id_init(bectx, ops, (void **)&access_ctx->id_ctx);
+    if (ret != EOK) {
+        DEBUG(1, ("sssm_ldap_id_init failed.\n"));
+        goto done;
+    }
+
+    access_ctx->filter = dp_opt_get_cstring(access_ctx->id_ctx->opts->basic,
+                                            SDAP_ACCESS_FILTER);
+    if (access_ctx->filter == NULL) {
+        /* It's okay if this is NULL. In that case we will simply act
+         * like the 'deny' provider.
+         */
+        DEBUG(0, ("Warning: access_provider=ldap set, "
+                  "but no ldap_access_filter configured. "
+                  "All domain users will be denied access."));
+    }
+
+    *ops = &sdap_access_ops;
+    *pvt_data = access_ctx;
+
+done:
+    if (ret != EOK) {
+        talloc_free(access_ctx);
+    }
+    return ret;
+}
+
 static void sdap_shutdown(struct be_req *req)
 {
     /* TODO: Clean up any internal data */
diff --git a/src/providers/ldap/sdap.h b/src/providers/ldap/sdap.h
index a4da43b351553966eea14a30c06d78b6836aa5e6..8fb623f55237c8da0f3ec6c8a64ca4dbc3dd94ec 100644
--- a/src/providers/ldap/sdap.h
+++ b/src/providers/ldap/sdap.h
@@ -151,6 +151,7 @@ enum sdap_basic_opt {
     SDAP_REFERRALS,
     SDAP_ACCOUNT_CACHE_EXPIRATION,
     SDAP_DNS_SERVICE_NAME,
+    SDAP_ACCESS_FILTER,
 
     SDAP_OPTS_BASIC /* opts counter */
 };
diff --git a/src/providers/ldap/sdap_access.c b/src/providers/ldap/sdap_access.c
new file mode 100644
index 0000000000000000000000000000000000000000..81e1779d817e2c47a63792334cf613fc33a899ed
--- /dev/null
+++ b/src/providers/ldap/sdap_access.c
@@ -0,0 +1,575 @@
+/*
+    SSSD
+
+    sdap_access.c
+
+    Authors:
+        Stephen Gallagher <sgallagh@redhat.com>
+
+    Copyright (C) 2010 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 <sys/param.h>
+#include <security/pam_modules.h>
+#include <talloc.h>
+#include <tevent.h>
+#include <errno.h>
+
+#include "util/util.h"
+#include "db/sysdb.h"
+#include "providers/ldap/ldap_common.h"
+#include "providers/ldap/sdap.h"
+#include "providers/ldap/sdap_access.h"
+#include "providers/ldap/sdap_async.h"
+#include "providers/data_provider.h"
+#include "providers/dp_backend.h"
+
+static void sdap_access_reply(struct be_req *be_req, int pam_status)
+{
+    struct pam_data *pd;
+    pd = talloc_get_type(be_req->req_data, struct pam_data);
+    pd->pam_status = pam_status;
+
+    if (pam_status == PAM_SUCCESS || pam_status == PAM_PERM_DENIED) {
+        be_req->fn(be_req, DP_ERR_OK, pam_status, NULL);
+    }
+
+    else {
+        be_req->fn(be_req, DP_ERR_FATAL, pam_status, NULL);
+    }
+}
+
+static struct tevent_req *sdap_access_send(TALLOC_CTX *mem_ctx,
+                                           struct be_req *breq,
+                                           const char *username);
+static void sdap_access_done(struct tevent_req *req);
+void sdap_pam_access_handler(struct be_req *breq)
+{
+    struct pam_data *pd;
+    struct tevent_req *req;
+
+    pd = talloc_get_type(breq->req_data, struct pam_data);
+
+    req = sdap_access_send(breq, breq, pd->user);
+    if (req == NULL) {
+        DEBUG(1, ("Unable to start sdap_access request\n"));
+        sdap_access_reply(breq, PAM_SYSTEM_ERR);
+        return;
+    }
+
+    tevent_req_set_callback(req, sdap_access_done, breq);
+}
+
+struct sdap_access_req_ctx {
+    const char *username;
+    const char *filter;
+    struct tevent_context *ev;
+    struct sdap_access_ctx *access_ctx;
+    struct sdap_id_ctx *sdap_ctx;
+    struct sysdb_handle *handle;
+    struct be_req *breq;
+    const char **attrs;
+    int pam_status;
+    bool cached_access;
+    char *basedn;
+};
+static void sdap_access_get_dn_done(void *pvt, int ldb_status,
+                                    struct ldb_result *res);
+static struct tevent_req *sdap_access_send(TALLOC_CTX *mem_ctx,
+                                           struct be_req *breq,
+                                           const char *username)
+{
+    errno_t ret;
+    struct sdap_access_req_ctx *state;
+    struct sdap_access_ctx *access_ctx;
+    struct tevent_req *req;
+
+    req = tevent_req_create(mem_ctx, &state, struct sdap_access_req_ctx);
+    if (req == NULL) {
+        return NULL;
+    }
+
+    access_ctx =
+            talloc_get_type(breq->be_ctx->bet_info[BET_ACCESS].pvt_bet_data,
+                            struct sdap_access_ctx);
+
+    if (access_ctx->filter == NULL || *access_ctx->filter == '\0') {
+        /* If no filter is set, default to restrictive */
+        DEBUG(6, ("No filter set. Access is denied.\n"));
+        state->pam_status = PAM_PERM_DENIED;
+        tevent_req_done(req);
+        tevent_req_post(req, breq->be_ctx->ev);
+        return req;
+    }
+
+    state->filter = access_ctx->filter;
+    state->breq = breq;
+    state->username = username;
+    state->pam_status = PAM_SYSTEM_ERR;
+    state->sdap_ctx = access_ctx->id_ctx;
+    state->ev = breq->be_ctx->ev;
+    state->access_ctx = access_ctx;
+
+    state->attrs = talloc_array(state, const char *, 3);
+    if (state->attrs == NULL) {
+        DEBUG(1, ("Could not allocate attributes\n"));
+        goto failed;
+    }
+
+    state->attrs[0] = SYSDB_ORIG_DN;
+    state->attrs[1] = SYSDB_LDAP_ACCESS;
+    state->attrs[2] = NULL;
+
+    DEBUG(6, ("Performing access check for user [%s]\n", username));
+
+    /* Get original user DN */
+    ret = sysdb_get_user_attr(state, breq->be_ctx->sysdb,
+                              breq->be_ctx->domain, username,
+                              state->attrs,
+                              sdap_access_get_dn_done, req);
+    return req;
+
+failed:
+    talloc_free(req);
+    return NULL;
+}
+
+static void sdap_access_connect_done(struct tevent_req *subreq);
+static void sdap_access_get_access_done(struct tevent_req *req);
+static void sdap_access_get_dn_done(void *pvt, int ldb_status,
+                                    struct ldb_result *res)
+{
+    errno_t ret;
+    struct sdap_access_req_ctx *state;
+    const char *basedn;
+    struct tevent_req *req;
+    struct tevent_req *subreq;
+
+    req = talloc_get_type(pvt, struct tevent_req);
+
+    state = tevent_req_data(req, struct sdap_access_req_ctx);
+    talloc_zfree(state->attrs);
+
+    /* Verify our results */
+    if (ldb_status == LDB_ERR_NO_SUCH_OBJECT) {
+        DEBUG(4, ("User not found in LDB.\n"));
+        ret = EOK;
+        state->pam_status = PAM_USER_UNKNOWN;
+        goto done;
+    }
+    else if (ldb_status != LDB_SUCCESS) {
+        DEBUG(1, ("LDB search failed.\n"));
+        ret = sysdb_error_to_errno(ldb_status);
+        state->pam_status = PAM_SYSTEM_ERR;
+        goto done;
+    }
+
+    /* Make sure we got exactly one result */
+    if (res->count < 1) {
+        DEBUG(4, ("User not found in LDB.\n"));
+        ret = EOK;
+        state->pam_status = PAM_USER_UNKNOWN;
+        goto done;
+    }
+
+    if (res->count > 1) {
+        DEBUG(1, ("More than one user found.\n"));
+        ret = EIO;
+        state->pam_status = PAM_SYSTEM_ERR;
+        goto done;
+    }
+
+    state->cached_access = ldb_msg_find_attr_as_bool(res->msgs[0],
+                                                     SYSDB_LDAP_ACCESS,
+                                                     false);
+
+    /* Ok, we have one result, check if we are online or offline */
+    if (be_is_offline(state->breq->be_ctx)) {
+        /* Ok, we're offline. Return from the cache */
+        if (state->cached_access) {
+            DEBUG(6, ("Access granted by cached credentials\n"));
+            ret = EOK;
+            state->pam_status = PAM_SUCCESS;
+            goto done;
+        }
+
+        /* Access denied */
+        DEBUG(6, ("Access denied by cached credentials\n"));
+        ret = EOK;
+        state->pam_status = PAM_PERM_DENIED;
+        goto done;
+    }
+
+    /* Perform online operation */
+    basedn = ldb_msg_find_attr_as_string(res->msgs[0],
+                                         SYSDB_ORIG_DN,
+                                         NULL);
+    if(basedn == NULL) {
+        DEBUG(1,("Could not find originalDN for user [%s]\n",
+                 state->username));
+        ret = EIO;
+        state->pam_status = PAM_SYSTEM_ERR;
+        goto done;
+    }
+
+    state->basedn = talloc_strdup(state, basedn);
+    if (state->basedn == NULL) {
+        DEBUG(1, ("Could not allocate memory for originalDN\n"));
+        ret = ENOMEM;
+        state->pam_status = PAM_SYSTEM_ERR;
+        goto done;
+    }
+
+    /* Construct the filter */
+    state->filter = talloc_asprintf(
+        state,
+        "(&(%s=%s)(objectclass=%s)(%s))",
+        state->sdap_ctx->opts->user_map[SDAP_AT_USER_NAME].name,
+        state->username,
+        state->sdap_ctx->opts->user_map[SDAP_OC_USER].name,
+        state->access_ctx->filter);
+    if (state->filter == NULL) {
+        DEBUG(0, ("Could not construct access filter\n"));
+        ret = ENOMEM;
+        state->pam_status = PAM_SYSTEM_ERR;
+        goto done;
+    }
+
+    DEBUG(6, ("Checking filter against LDAP\n"));
+
+    /* Check whether we have an active LDAP connection */
+    if (state->sdap_ctx->gsh == NULL || ! state->sdap_ctx->gsh->connected) {
+        subreq = sdap_cli_connect_send(state, state->ev,
+                                       state->sdap_ctx->opts,
+                                       state->sdap_ctx->be,
+                                       state->sdap_ctx->service,
+                                       NULL);
+        if (!subreq) {
+            DEBUG(1, ("sdap_cli_connect_send failed.\n"));
+            ret = EIO;
+            state->pam_status = PAM_SYSTEM_ERR;
+            goto done;
+        }
+
+        tevent_req_set_callback(subreq, sdap_access_connect_done, req);
+        return;
+    }
+
+    /* Make the LDAP request */
+    subreq = sdap_get_generic_send(state,
+                                   state->ev,
+                                   state->sdap_ctx->opts,
+                                   state->sdap_ctx->gsh,
+                                   basedn, LDAP_SCOPE_BASE,
+                                   state->filter, NULL,
+                                   NULL, 0);
+    if (subreq == NULL) {
+        DEBUG(1, ("Could not start LDAP communication\n"));
+        ret = EIO;
+        state->pam_status = PAM_SYSTEM_ERR;
+        goto done;
+    }
+
+    tevent_req_set_callback(subreq, sdap_access_get_access_done, req);
+    return;
+
+done:
+    if (ret == EOK) {
+        tevent_req_done(req);
+    }
+    else {
+        tevent_req_error(req, ret);
+    }
+}
+
+static void sdap_access_connect_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct sdap_access_req_ctx *state =
+            tevent_req_data(req, struct sdap_access_req_ctx);
+
+    ret = sdap_cli_connect_recv(subreq, state->sdap_ctx,
+                                &state->sdap_ctx->gsh, NULL);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        /* Could not connect to LDAP. Mark as offline and return
+         * from cache.
+         */
+        be_mark_offline(state->breq->be_ctx);
+        if (state->cached_access) {
+            DEBUG(6, ("Access granted by cached credentials\n"));
+            state->pam_status = PAM_SUCCESS;
+            tevent_req_done(req);
+            return;
+        }
+
+        /* Access denied */
+        DEBUG(6, ("Access denied by cached credentials\n"));
+        state->pam_status = PAM_PERM_DENIED;
+        tevent_req_done(req);
+    }
+
+    /* Connection to LDAP succeeded
+     * Send filter request
+     */
+    subreq = sdap_get_generic_send(state,
+                                   state->ev,
+                                   state->sdap_ctx->opts,
+                                   state->sdap_ctx->gsh,
+                                   state->basedn,
+                                   LDAP_SCOPE_BASE,
+                                   state->filter, NULL,
+                                   NULL, 0);
+    if (subreq == NULL) {
+        DEBUG(1, ("Could not start LDAP communication\n"));
+        state->pam_status = PAM_SYSTEM_ERR;
+        tevent_req_error(req, EIO);
+        return;
+    }
+
+    tevent_req_set_callback(subreq, sdap_access_get_access_done, req);
+}
+
+static void sdap_access_save_cache(struct tevent_req *req);
+static void sdap_access_get_access_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    size_t num_results;
+    bool found = false;
+    struct sysdb_attrs **results;
+    struct tevent_req *req =
+            tevent_req_callback_data(subreq, struct tevent_req);
+    struct sdap_access_req_ctx *state =
+            tevent_req_data(req, struct sdap_access_req_ctx);
+    ret = sdap_get_generic_recv(subreq, state,
+                                &num_results, &results);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(1, ("sdap_get_generic_send() returned error [%d][%s]",
+                  ret, strerror(ret)));
+        state->pam_status = PAM_SYSTEM_ERR;
+        goto done;
+    }
+
+    /* Check the number of responses we got
+     * If it's exactly 1, we passed the check
+     * If it's < 1, we failed the check
+     * Anything else is an error
+     */
+    if (num_results < 1) {
+        DEBUG(4, ("User [%s] was not found with the specified filter. "
+                  "Denying access.\n", state->username));
+        found = false;
+    }
+    else if (results == NULL) {
+        DEBUG(1, ("num_results > 0, but results is NULL\n"));
+        state->pam_status = PAM_SYSTEM_ERR;
+        goto done;
+    }
+    else if (num_results > 1) {
+        /* It should not be possible to get more than one reply
+         * here, since we're doing a base-scoped search
+         */
+        DEBUG(1, ("Received multiple replies\n"));
+        state->pam_status = PAM_SYSTEM_ERR;
+        goto done;
+    }
+    else { /* Ok, we got a single reply */
+        found = true;
+    }
+
+    if (found) {
+        /* Save "allow" to the cache for future offline
+         * access checks.
+         */
+        DEBUG(6, ("Access granted by online lookup\n"));
+        state->pam_status = PAM_SUCCESS;
+    }
+    else {
+        /* Save "disallow" to the cache for future offline
+         * access checks.
+         */
+        DEBUG(6, ("Access denied by online lookup\n"));
+        state->pam_status = PAM_PERM_DENIED;
+    }
+
+    /* Start a transaction to cache the access result */
+    subreq = sysdb_transaction_send(state, state->ev,
+                                    state->breq->be_ctx->sysdb);
+    if (subreq == NULL) {
+        /* Failing to save the cache is non-fatal.
+         * Just return the result.
+         */
+        ret = EOK;
+        DEBUG(1, ("Failed to create transaction for user access attr\n"));
+        goto done;
+    }
+    tevent_req_set_callback(subreq, sdap_access_save_cache, req);
+    return;
+
+done:
+    if (ret == EOK) {
+        tevent_req_done(req);
+    }
+    else {
+        tevent_req_error(req, ret);
+    }
+}
+
+static void sdap_access_cache_commit(struct tevent_req *subreq);
+static void sdap_access_save_cache(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct sysdb_attrs *attrs;
+    struct tevent_req *req =
+            tevent_req_callback_data(subreq, struct tevent_req);
+    struct sdap_access_req_ctx *state =
+            tevent_req_data(req, struct sdap_access_req_ctx);
+
+    ret = sysdb_transaction_recv(subreq, state, &state->handle);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        /* Failing to save the cache is non-fatal.
+         * Just return the result.
+         */
+        ret = EOK;
+        DEBUG(1, ("Failed to create transaction for user access attr\n"));
+        goto done;
+    }
+
+    attrs = sysdb_new_attrs(state);
+    if (attrs == NULL) {
+        ret = ENOMEM;
+        DEBUG(1, ("Could not set up attrs\n"));
+        goto done;
+    }
+
+    ret = sysdb_attrs_add_uint32(attrs, SYSDB_LDAP_ACCESS,
+                                 state->pam_status == PAM_SUCCESS ?
+                                                      true :
+                                                      false);
+    if (ret != EOK) {
+        DEBUG(1, ("Could not set up attrs\n"));
+        goto done;
+    }
+
+    subreq = sysdb_set_user_attr_send(attrs,
+                                      state->ev,
+                                      state->handle,
+                                      state->breq->be_ctx->domain,
+                                      state->username,
+                                      attrs, SYSDB_MOD_REP);
+    if (subreq == NULL) {
+        /* Failing to save to the cache is non-fatal.
+         * Just return the result.
+         */
+        DEBUG(1, ("Failed to set user access attribute\n"));
+        goto done;
+    }
+    tevent_req_set_callback(subreq, sdap_access_cache_commit, req);
+    return;
+
+done:
+    if (ret == EOK) {
+        tevent_req_done(req);
+    }
+    else {
+        tevent_req_error(req, ret);
+    }
+}
+
+static void sdap_access_cache_done(struct tevent_req *subreq);
+static void sdap_access_cache_commit(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req =
+            tevent_req_callback_data(subreq, struct tevent_req);
+    struct sdap_access_req_ctx *state =
+            tevent_req_data(req, struct sdap_access_req_ctx);
+
+    ret = sysdb_set_entry_attr_recv(subreq);
+    talloc_zfree(subreq);
+
+    if(ret != EOK) {
+        goto failed;
+    }
+
+    subreq = sysdb_transaction_commit_send(state, state->ev, state->handle);
+    if (subreq == NULL) {
+        goto failed;
+    }
+    tevent_req_set_callback(subreq,sdap_access_cache_done,req);
+    return;
+
+failed:
+    /* Failing to save to the cache is non-fatal.
+     * Just return the result.
+     */
+    DEBUG(1, ("Failed to set user access attribute\n"));
+    tevent_req_done(req);
+    return;
+}
+
+static void sdap_access_cache_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req =
+            tevent_req_callback_data(subreq, struct tevent_req);
+
+    ret = sysdb_transaction_commit_recv(subreq);
+    talloc_zfree(subreq);
+
+    if(ret != EOK) {
+        /* Failing to save to the cache is non-fatal */
+        DEBUG(1,("Unable to save access results to the cache\n"));
+    }
+    else {
+        DEBUG(6, ("Saved access result to the user cache\n"));
+    }
+
+    tevent_req_done(req);
+}
+
+static errno_t sdap_access_recv(struct tevent_req *req, int *pam_status)
+{
+    struct sdap_access_req_ctx *state =
+            tevent_req_data(req, struct sdap_access_req_ctx);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    *pam_status = state->pam_status;
+
+    return EOK;
+}
+
+static void sdap_access_done(struct tevent_req *req)
+{
+    errno_t ret;
+    int pam_status;
+    struct be_req *breq =
+            tevent_req_callback_data(req, struct be_req);
+
+    ret = sdap_access_recv(req, &pam_status);
+    talloc_zfree(req);
+    if (ret != EOK) {
+        DEBUG(1, ("Error retrieving access check result.\n"));
+        pam_status = PAM_SYSTEM_ERR;
+    }
+
+    sdap_access_reply(breq, pam_status);
+}
diff --git a/src/providers/ldap/sdap_access.h b/src/providers/ldap/sdap_access.h
new file mode 100644
index 0000000000000000000000000000000000000000..5dbe8646188519748ed1c7e6647409f13c876132
--- /dev/null
+++ b/src/providers/ldap/sdap_access.h
@@ -0,0 +1,39 @@
+/*
+    SSSD
+
+    sdap_access.h
+
+    Authors:
+        Stephen Gallagher <sgallagh@redhat.com>
+
+    Copyright (C) 2010 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_ACCESS_H_
+#define SDAP_ACCESS_H_
+
+#include "providers/dp_backend.h"
+
+#define SYSDB_LDAP_ACCESS "ldap_access_allow"
+
+struct sdap_access_ctx {
+    struct sdap_id_ctx *id_ctx;
+    const char *filter;
+};
+
+void ldap_pam_access_handler(struct be_req *breq);
+
+#endif /* SDAP_ACCESS_H_ */
-- 
1.7.0.1

