>From ed81ba88e047a23160f3dc76306040eff7d59064 Mon Sep 17 00:00:00 2001
From: Pavel Reichl <preichl@redhat.com>
Date: Tue, 20 Jan 2015 18:34:44 -0500
Subject: [PATCH 2/2] SDAP: Lock out ssh keys when account naturally expires

Resolves:
https://fedorahosted.org/sssd/ticket/2534
---
 Makefile.am                      |   6 +-
 src/man/sssd-ldap.5.xml          |  13 +
 src/providers/ldap/ldap_init.c   |   2 +
 src/providers/ldap/sdap_access.c | 616 +++++++++++++++++++++++++++++++++++++++
 src/providers/ldap/sdap_access.h |   3 +
 5 files changed, 638 insertions(+), 2 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index 8d557b257c18b62c683e3007c820a1f93ff0595d..98503e9989c60ed808151e7e9c0225d7e83f5cd7 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2455,14 +2455,16 @@ libsss_ldap_common_la_SOURCES = \
     src/providers/ldap/sdap_domain.c \
     src/providers/ldap/sdap.c \
     src/util/user_info_msg.c \
-    src/util/sss_ldap.c
+    src/util/sss_ldap.c \
+    src/util/util.c
 libsss_ldap_common_la_CFLAGS = \
     $(KRB5_CFLAGS)
 libsss_ldap_common_la_LIBADD = \
     $(OPENLDAP_LIBS) \
     $(KRB5_LIBS) \
     libsss_krb5_common.la \
-    libsss_idmap.la
+    libsss_idmap.la \
+    libsss_util.la
 libsss_ldap_common_la_LDFLAGS = \
     -avoid-version
 
diff --git a/src/man/sssd-ldap.5.xml b/src/man/sssd-ldap.5.xml
index 9f2e9ac34add13e40d316374094024afdcc4ae31..ce5e03bf9cd5e57d015d064ad863aad05e1f4e24 100644
--- a/src/man/sssd-ldap.5.xml
+++ b/src/man/sssd-ldap.5.xml
@@ -1955,6 +1955,19 @@ ldap_access_filter = (employeeType=admin)
                             be set for this feature to work.
                         </para>
                         <para>
+                            <emphasis>ppolicy</emphasis>: use account locking.
+                            If set, this option denies access in case that ldap
+                            attribute 'pwdAccountLockedTime' is present and has
+                            value of '000001010000Z' or any time event in the past.
+                            'pwdAccountLockedTime' attribute must end with 'Z'
+                            as only zulu time is currently suported.
+                            Please see the option ldap_pwdlockout_dn.
+
+                            Please note that 'access_provider = ldap' must
+                            be set for this feature to work.
+                        </para>
+
+                        <para>
                             <emphasis>expire</emphasis>: use
                             ldap_account_expire_policy
                         </para>
diff --git a/src/providers/ldap/ldap_init.c b/src/providers/ldap/ldap_init.c
index 44333a9a3a45de16aaaf83fecaea4817cebc90d4..dce7770a521389797705a4dc909b97da94531ce1 100644
--- a/src/providers/ldap/ldap_init.c
+++ b/src/providers/ldap/ldap_init.c
@@ -423,6 +423,8 @@ int sssm_ldap_access_init(struct be_ctx *bectx,
             access_ctx->access_rule[c] = LDAP_ACCESS_HOST;
         } else if (strcasecmp(order_list[c], LDAP_ACCESS_LOCK_NAME) == 0) {
             access_ctx->access_rule[c] = LDAP_ACCESS_LOCKOUT;
+        } else if (strcasecmp(order_list[c], LDAP_ACCESS_PPOLICY_NAME) == 0) {
+            access_ctx->access_rule[c] = LDAP_ACCESS_PPOLICY;
         } else {
             DEBUG(SSSDBG_CRIT_FAILURE,
                   "Unexpected access rule name [%s].\n", order_list[c]);
diff --git a/src/providers/ldap/sdap_access.c b/src/providers/ldap/sdap_access.c
index 52ea50ae22dcddde41d9567b21d726e35f8ed542..b2d43a1a5bd81a5a8e8af73490f268f2a50db3fd 100644
--- a/src/providers/ldap/sdap_access.c
+++ b/src/providers/ldap/sdap_access.c
@@ -32,6 +32,7 @@
 #include <errno.h>
 
 #include "util/util.h"
+#include "util/strtonum.h"
 #include "db/sysdb.h"
 #include "providers/ldap/ldap_common.h"
 #include "providers/ldap/sdap.h"
@@ -62,6 +63,16 @@ sdap_access_lock_send(TALLOC_CTX *mem_ctx,
                       const char *username,
                       struct ldb_message *user_entry);
 
+static struct tevent_req *
+sdap_access_ppolicy_send(TALLOC_CTX *mem_ctx,
+                         struct tevent_context *ev,
+                         struct be_ctx *be_ctx,
+                         struct sss_domain_info *domain,
+                         struct sdap_access_ctx *access_ctx,
+                         struct sdap_id_conn_ctx *conn,
+                         const char *username,
+                         struct ldb_message *user_entry);
+
 static struct tevent_req *sdap_access_filter_send(TALLOC_CTX *mem_ctx,
                                              struct tevent_context *ev,
                                              struct be_ctx *be_ctx,
@@ -75,6 +86,8 @@ static errno_t sdap_access_filter_recv(struct tevent_req *req);
 
 static errno_t sdap_access_lock_recv(struct tevent_req *req);
 
+static errno_t sdap_access_ppolicy_recv(struct tevent_req *req);
+
 static errno_t sdap_account_expired(struct sdap_access_ctx *access_ctx,
                                     struct pam_data *pd,
                                     struct ldb_message *user_entry);
@@ -86,6 +99,7 @@ static errno_t sdap_access_host(struct ldb_message *user_entry);
 
 enum sdap_access_control_type {
     SDAP_ACCESS_CONTROL_FILTER,
+    SDAP_ACCESS_CONTROL_PPOLICY,
     SDAP_ACCESS_CONTROL_PPOLICY_LOCK,
 };
 
@@ -215,6 +229,24 @@ static errno_t sdap_access_check_next_rule(struct sdap_access_req_ctx *state,
             tevent_req_set_callback(subreq, sdap_access_done, req);
             return EAGAIN;
 
+        case LDAP_ACCESS_PPOLICY:
+            subreq = sdap_access_ppolicy_send(state, state->ev, state->be_ctx,
+                                              state->domain,
+                                              state->access_ctx,
+                                              state->conn,
+                                              state->pd->user,
+                                              state->user_entry);
+            if (subreq == NULL) {
+                DEBUG(SSSDBG_CRIT_FAILURE,
+                      "sdap_access_ppolicy_send failed.\n");
+                return ENOMEM;
+            }
+
+            state->ac_type = SDAP_ACCESS_CONTROL_PPOLICY;
+
+            tevent_req_set_callback(subreq, sdap_access_done, req);
+            return EAGAIN;
+
         case LDAP_ACCESS_FILTER:
             subreq = sdap_access_filter_send(state, state->ev, state->be_ctx,
                                              state->domain,
@@ -274,6 +306,9 @@ static void sdap_access_done(struct tevent_req *subreq)
     case SDAP_ACCESS_CONTROL_PPOLICY_LOCK:
         ret = sdap_access_lock_recv(subreq);
         break;
+    case SDAP_ACCESS_CONTROL_PPOLICY:
+        ret = sdap_access_ppolicy_recv(subreq);
+        break;
     default:
         ret = EINVAL;
         DEBUG(SSSDBG_MINOR_FAILURE, "Unknown access control type: %d.\n",
@@ -733,6 +768,8 @@ static errno_t sdap_access_decide_offline(bool cached_ac);
 static int sdap_access_filter_retry(struct tevent_req *req);
 static void sdap_access_lock_connect_done(struct tevent_req *subreq);
 static errno_t sdap_access_lock_get_lockout_step(struct tevent_req *req);
+static void sdap_access_ppolicy_connect_done(struct tevent_req *subreq);
+static errno_t sdap_access_ppolicy_get_lockout_step(struct tevent_req *req);
 static void sdap_access_filter_connect_done(struct tevent_req *subreq);
 static void sdap_access_filter_done(struct tevent_req *req);
 static struct tevent_req *sdap_access_filter_send(TALLOC_CTX *mem_ctx,
@@ -1733,6 +1770,585 @@ static errno_t sdap_access_lock_recv(struct tevent_req *req)
     return EOK;
 }
 
+static void sdap_access_ppolicy_get_lockout_done(struct tevent_req *subreq);
+static int sdap_access_ppolicy_retry(struct tevent_req *req);
+static errno_t sdap_access_ppolicy_step(struct tevent_req *req);
+static void sdap_access_ppolicy_step_done(struct tevent_req *subreq);
+static void sdap_access_ppolicy_connect_done(struct tevent_req *subreq);
+
+struct sdap_access_ppolicy_req_ctx {
+    const char *username;
+    const char *filter;
+    struct tevent_context *ev;
+    struct sdap_access_ctx *access_ctx;
+    struct sdap_options *opts;
+    struct sdap_id_conn_ctx *conn;
+    struct sdap_id_op *sdap_op;
+    struct sysdb_handle *handle;
+    struct sss_domain_info *domain;
+    /* cached results of access control checks */
+    bool cached_access;
+    const char *basedn;
+    /* default DNs to ppolicy */
+    const char **ppolicy_dns;
+    unsigned int ppolicy_dns_index;
+};
+
+static struct tevent_req *
+sdap_access_ppolicy_send(TALLOC_CTX *mem_ctx,
+                      struct tevent_context *ev,
+                      struct be_ctx *be_ctx,
+                      struct sss_domain_info *domain,
+                      struct sdap_access_ctx *access_ctx,
+                      struct sdap_id_conn_ctx *conn,
+                      const char *username,
+                      struct ldb_message *user_entry)
+{
+    struct sdap_access_ppolicy_req_ctx *state;
+    struct tevent_req *req;
+    errno_t ret;
+
+    req = tevent_req_create(mem_ctx,
+                            &state, struct sdap_access_ppolicy_req_ctx);
+    if (req == NULL) {
+        return NULL;
+    }
+
+    state->filter = NULL;
+    state->username = username;
+    state->opts = access_ctx->id_ctx->opts;
+    state->conn = conn;
+    state->ev = ev;
+    state->access_ctx = access_ctx;
+    state->domain = domain;
+    state->ppolicy_dns_index = 0;
+
+    DEBUG(SSSDBG_TRACE_FUNC,
+          "Performing access ppolicy check for user [%s]\n", username);
+
+    state->cached_access = ldb_msg_find_attr_as_bool(
+        user_entry, SYSDB_LDAP_ACCESS_CACHED_LOCKOUT, false);
+
+    /* Ok, we have one result, check if we are online or offline */
+    if (be_is_offline(be_ctx)) {
+        /* Ok, we're offline. Return from the cache */
+        ret = sdap_access_decide_offline(state->cached_access);
+        goto done;
+    }
+
+    ret = sdap_get_basedn_user_entry(user_entry, state->username,
+                                     &state->basedn);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    DEBUG(SSSDBG_TRACE_FUNC, "Checking ppolicy against LDAP\n");
+
+    state->sdap_op = sdap_id_op_create(state,
+                                       state->conn->conn_cache);
+    if (!state->sdap_op) {
+        DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n");
+        ret = ENOMEM;
+        goto done;
+    }
+
+    ret = sdap_access_ppolicy_retry(req);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    return req;
+
+done:
+    if (ret == EOK) {
+        tevent_req_done(req);
+    } else {
+        tevent_req_error(req, ret);
+    }
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static int sdap_access_ppolicy_retry(struct tevent_req *req)
+{
+    struct sdap_access_ppolicy_req_ctx *state;
+    struct tevent_req *subreq;
+    int ret;
+
+    state = tevent_req_data(req, struct sdap_access_ppolicy_req_ctx);
+    subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret);
+    if (!subreq) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "sdap_id_op_connect_send failed: %d (%s)\n", ret, strerror(ret));
+        return ret;
+    }
+
+    tevent_req_set_callback(subreq, sdap_access_ppolicy_connect_done, req);
+    return EOK;
+}
+
+static void sdap_access_ppolicy_connect_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req;
+    struct sdap_access_ppolicy_req_ctx *state;
+    int ret, dp_error;
+    const char *ppolicy_dn;
+
+    req = tevent_req_callback_data(subreq, struct tevent_req);
+    state = tevent_req_data(req, struct sdap_access_ppolicy_req_ctx);
+
+    ret = sdap_id_op_connect_recv(subreq, &dp_error);
+    talloc_zfree(subreq);
+
+    if (ret != EOK) {
+        if (dp_error == DP_ERR_OFFLINE) {
+            ret = sdap_access_decide_offline(state->cached_access);
+            if (ret == EOK) {
+                tevent_req_done(req);
+                return;
+            }
+        }
+
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    ppolicy_dn = dp_opt_get_string(state->opts->basic,
+                                   SDAP_PWDLOCKOUT_DN);
+
+    /* option was configured */
+    if (ppolicy_dn != NULL) {
+        state->ppolicy_dns = talloc_array(state, const char*, 2);
+        if (state->ppolicy_dns == NULL) {
+            DEBUG(SSSDBG_CRIT_FAILURE, "Could not allocate ppolicy_dns.\n");
+            tevent_req_error(req, ERR_ACCESS_DENIED);
+            return;
+        }
+
+        state->ppolicy_dns[0] = ppolicy_dn;
+        state->ppolicy_dns[1] = NULL;
+
+    } else {
+        /* try to determine default value */
+        DEBUG(SSSDBG_CONF_SETTINGS,
+              "ldap_pwdlockout_dn was not defined in configuration file.\n");
+
+        state->ppolicy_dns = get_default_ppolicy_dns(state, state->opts->sdom);
+        if (state->ppolicy_dns == NULL) {
+            tevent_req_error(req, ERR_ACCESS_DENIED);
+            return;
+        }
+    }
+
+    /* Connection to LDAP succeeded
+     * Send 'pwdLockout' request
+     */
+    ret = sdap_access_ppolicy_get_lockout_step(req);
+    if (ret != EOK && ret != EAGAIN) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "sdap_access_ppolicy_get_lockout_step failed: [%d][%s]\n",
+              ret, strerror(ret));
+        tevent_req_error(req, ERR_ACCESS_DENIED);
+        return;
+    }
+}
+
+static errno_t
+sdap_access_ppolicy_get_lockout_step(struct tevent_req *req)
+{
+    const char *attrs[] = { SYSDB_LDAP_ACCESS_LOCKOUT, NULL };
+    struct sdap_access_ppolicy_req_ctx *state;
+    struct tevent_req *subreq;
+    errno_t ret;
+
+    state = tevent_req_data(req, struct sdap_access_ppolicy_req_ctx);
+
+    /* no more DNs to try */
+    if (state->ppolicy_dns[state->ppolicy_dns_index] == NULL) {
+        ret = EOK;
+        goto done;
+    }
+
+    DEBUG(SSSDBG_CONF_SETTINGS,
+          "Trying to find out if ppolicy is enabled using the DN: %s\n",
+          state->ppolicy_dns[state->ppolicy_dns_index]);
+
+    subreq = sdap_get_generic_send(state,
+                                   state->ev,
+                                   state->opts,
+                                   sdap_id_op_handle(state->sdap_op),
+                                   state->ppolicy_dns[state->ppolicy_dns_index],
+                                   LDAP_SCOPE_BASE,
+                                   NULL, attrs,
+                                   NULL, 0,
+                                   dp_opt_get_int(state->opts->basic,
+                                                  SDAP_SEARCH_TIMEOUT),
+                                   false);
+    if (subreq == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Could not start LDAP communication\n");
+        tevent_req_error(req, EIO);
+        ret = EIO;
+        goto done;
+    }
+
+    /* try next basedn */
+    state->ppolicy_dns_index++;
+    tevent_req_set_callback(subreq, sdap_access_ppolicy_get_lockout_done, req);
+
+    ret = EAGAIN;
+
+done:
+    return ret;
+}
+
+static void sdap_access_ppolicy_get_lockout_done(struct tevent_req *subreq)
+{
+    int ret, tret, dp_error;
+    size_t num_results;
+    bool pwdLockout = false;
+    struct sysdb_attrs **results;
+    struct tevent_req *req;
+    struct sdap_access_ppolicy_req_ctx *state;
+
+    req = tevent_req_callback_data(subreq, struct tevent_req);
+    state = tevent_req_data(req, struct sdap_access_ppolicy_req_ctx);
+
+    ret = sdap_get_generic_recv(subreq, state, &num_results, &results);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "Cannot retrieve ppolicy\n");
+        ret = ERR_NETWORK_IO;
+        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
+     */
+    /* Didn't find ppolicy attribute */
+    if (num_results < 1) {
+        /* Try using next $search_base */
+        ret = sdap_access_ppolicy_get_lockout_step(req);
+        if (ret == EOK) {
+            /* No more search bases to try */
+            DEBUG(SSSDBG_CONF_SETTINGS,
+                  "[%s] was not found. Granting access.\n",
+                  SYSDB_LDAP_ACCESS_LOCKOUT);
+        } else {
+            if (ret != EAGAIN) {
+                DEBUG(SSSDBG_CRIT_FAILURE,
+                      "sdap_access_ppolicy_get_lockout_step failed: [%d][%s]\n",
+                      ret, strerror(ret));
+            }
+            goto done;
+        }
+    } else if (results == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "num_results > 0, but results is NULL\n");
+        ret = ERR_INTERNAL;
+        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(SSSDBG_CRIT_FAILURE, "Received multiple replies\n");
+        ret = ERR_INTERNAL;
+        goto done;
+    } else { /* Ok, we got a single reply */
+        ret = sysdb_attrs_get_bool(results[0], SYSDB_LDAP_ACCESS_LOCKOUT,
+                                   &pwdLockout);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_MINOR_FAILURE,
+                  "Error reading %s: [%s]\n", SYSDB_LDAP_ACCESS_LOCKOUT,
+                  strerror(ret));
+            ret = ERR_INTERNAL;
+            goto done;
+        }
+    }
+
+    if (pwdLockout) {
+        DEBUG(SSSDBG_TRACE_FUNC,
+              "Password policy is enabled on LDAP server.\n");
+
+        /* ppolicy is enabled => find out if account is locked */
+        ret = sdap_access_ppolicy_step(req);
+        if (ret != EOK && ret != EAGAIN) {
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  "sdap_access_ppolicy_step failed: [%d][%s].\n",
+                  ret, strerror(ret));
+        }
+        goto done;
+    } else {
+        DEBUG(SSSDBG_TRACE_FUNC,
+              "Password policy is disabled on LDAP server "
+              "- storing 'access granted' in sysdb.\n");
+        tret = sdap_save_user_cache_bool(state->domain, state->username,
+                                         SYSDB_LDAP_ACCESS_CACHED_LOCKOUT,
+                                         true);
+        if (tret != EOK) {
+            /* Failing to save to the cache is non-fatal.
+             * Just return the result.
+             */
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  "Failed to set user locked attribute\n");
+            goto done;
+        }
+
+        ret = EOK;
+        goto done;
+    }
+
+done:
+    if (ret != EAGAIN) {
+        /* release connection */
+        tret = sdap_id_op_done(state->sdap_op, ret, &dp_error);
+        if (tret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  "sdap_get_generic_send() returned error [%d][%s]\n",
+                  ret, sss_strerror(ret));
+        }
+
+        if (ret == EOK) {
+            tevent_req_done(req);
+        } else {
+            tevent_req_error(req, ret);
+        }
+    }
+}
+
+errno_t sdap_access_ppolicy_step(struct tevent_req *req)
+{
+    errno_t ret;
+    struct tevent_req *subreq;
+    struct sdap_access_ppolicy_req_ctx *state;
+    const char *attrs[] = { SYSDB_LDAP_ACCESS_LOCKED_TIME,
+                            SYSDB_LDAP_ACESS_LOCKOUT_DURATION,
+                            NULL };
+
+    state = tevent_req_data(req, struct sdap_access_ppolicy_req_ctx);
+
+    subreq = sdap_get_generic_send(state,
+                                   state->ev,
+                                   state->opts,
+                                   sdap_id_op_handle(state->sdap_op),
+                                   state->basedn,
+                                   LDAP_SCOPE_BASE,
+                                   NULL, attrs,
+                                   NULL, 0,
+                                   dp_opt_get_int(state->opts->basic,
+                                                  SDAP_SEARCH_TIMEOUT),
+                                   false);
+
+    if (subreq == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "sdap_access_ppolicy_send failed.\n");
+        ret = ENOMEM;
+        goto done;
+    }
+
+    tevent_req_set_callback(subreq, sdap_access_ppolicy_step_done, req);
+    ret = EAGAIN;
+
+done:
+    return ret;
+}
+
+static errno_t
+is_account_locked(const char *pwdAccountLockedTime,
+                  const char *pwdAccountLockedDurationTime,
+                  bool *_locked)
+{
+    errno_t ret;
+    time_t lock_time;
+    time_t duration;
+    time_t now;
+    bool locked;
+
+    /* Default action is to consider account to be locked. */
+    locked = true;
+
+    /* account is permanently locked */
+    if (strcasecmp(pwdAccountLockedTime,
+                   PERMANENTLY_LOCKED_ACCOUNT) == 0) {
+        ret = EOK;
+        goto done;
+    }
+
+    /* Account may be locked out from natural reasons (too many attempts,
+     * expired password). In this case, pwdAccountLockedTime is also set,
+     * to the time of lock out.
+     */
+    ret = sss_utc_to_time_t(pwdAccountLockedTime, "%Y%m%d%H%M%SZ",
+                       &lock_time);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_TRACE_FUNC, "sss_utc_to_time_t failed with %d:%s.\n",
+              ret, sss_strerror(ret));
+        goto done;
+    }
+
+    now = time(NULL);
+
+    /* Account was NOT locked in past. */
+    if (difftime(lock_time, now) > 0.0) {
+        locked = false;
+    } else if (pwdAccountLockedDurationTime != NULL) {
+        errno = 0;
+        duration = strtouint32(pwdAccountLockedDurationTime, NULL, 0);
+        if (errno) {
+            ret = errno;
+            goto done;
+        }
+        /* Lockout has expired */
+        if (duration != 0 && difftime(now, lock_time) > duration) {
+            locked = false;
+        }
+    }
+
+    ret = EOK;
+
+done:
+    if (ret == EOK) {
+        *_locked = locked;
+    }
+
+    return ret;
+}
+
+static void sdap_access_ppolicy_step_done(struct tevent_req *subreq)
+{
+    int ret, tret, dp_error;
+    size_t num_results;
+    bool locked = false;
+    const char *pwdAccountLockedTime;
+    const char *pwdAccountLockedDurationTime;
+    struct sysdb_attrs **results;
+    struct tevent_req *req;
+    struct sdap_access_ppolicy_req_ctx *state;
+
+    req = tevent_req_callback_data(subreq, struct tevent_req);
+    state = tevent_req_data(req, struct sdap_access_ppolicy_req_ctx);
+
+    ret = sdap_get_generic_recv(subreq, state, &num_results, &results);
+    talloc_zfree(subreq);
+
+    ret = sdap_id_op_done(state->sdap_op, ret, &dp_error);
+    if (ret != EOK) {
+        if (dp_error == DP_ERR_OK) {
+            /* retry */
+            tret = sdap_access_lock_retry(req);
+            if (tret == EOK) {
+                return;
+            }
+        } else if (dp_error == DP_ERR_OFFLINE) {
+            ret = sdap_access_decide_offline(state->cached_access);
+        } else {
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  "sdap_get_generic_send() returned error [%d][%s]\n",
+                  ret, sss_strerror(ret));
+        }
+
+        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(SSSDBG_CONF_SETTINGS,
+              "User [%s] was not found with the specified filter. "
+                  "Denying access.\n", state->username);
+    } else if (results == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "num_results > 0, but results is NULL\n");
+        ret = ERR_INTERNAL;
+        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(SSSDBG_CRIT_FAILURE, "Received multiple replies\n");
+        ret = ERR_INTERNAL;
+        goto done;
+    } else { /* Ok, we got a single reply */
+        ret = sysdb_attrs_get_string(results[0], SYSDB_LDAP_ACESS_LOCKOUT_DURATION,
+                                     &pwdAccountLockedDurationTime);
+        if (ret != EOK) {
+            /* This attribute might not be set even if account is locked */
+            pwdAccountLockedDurationTime = NULL;
+        }
+
+        ret = sysdb_attrs_get_string(results[0], SYSDB_LDAP_ACCESS_LOCKED_TIME,
+                                     &pwdAccountLockedTime);
+        if (ret == EOK) {
+
+            ret = is_account_locked(pwdAccountLockedTime,
+                                    pwdAccountLockedDurationTime,
+                                    &locked);
+            if (ret != EOK) {
+                if (ret == ERR_INVALID_LOCK_TIME) {
+                    DEBUG(SSSDBG_MINOR_FAILURE,
+                          "timezone specifier in ppolicy is not supported\n");
+                } else {
+                    DEBUG(SSSDBG_MINOR_FAILURE,
+                          "is_account_locked failed: %d:[%s].\n",
+                          ret, sss_strerror(ret));
+                }
+
+                DEBUG(SSSDBG_MINOR_FAILURE,
+                      "Account will be considered to be locked.\n");
+                locked = true;
+            }
+        } else {
+            /* Attribute SYSDB_LDAP_ACCESS_LOCKED_TIME in not be present unless
+             * user's account is blocked by password policy.
+             */
+            DEBUG(SSSDBG_TRACE_INTERNAL,
+                  "Attribute %s failed to be obtained - [%d][%s].\n",
+                  SYSDB_LDAP_ACCESS_LOCKED_TIME, ret, strerror(ret));
+        }
+    }
+
+    if (locked) {
+        DEBUG(SSSDBG_TRACE_FUNC,
+              "Access denied by online lookup - account is locked.\n");
+        ret = ERR_ACCESS_DENIED;
+    } else {
+        DEBUG(SSSDBG_TRACE_FUNC,
+              "Access granted by online lookup - account is not locked.\n");
+        ret = EOK;
+    }
+
+    /* Save '!locked' to the cache for future offline access checks.
+     * Locked == true => access denied,
+     * Locked == false => access granted
+     */
+    tret = sdap_save_user_cache_bool(state->domain, state->username,
+                                     SYSDB_LDAP_ACCESS_CACHED_LOCKOUT,
+                                     !locked);
+
+    if (tret != EOK) {
+        /* Failing to save to the cache is non-fatal.
+         * Just return the result.
+         */
+        DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set user locked attribute\n");
+        goto done;
+    }
+
+done:
+    if (ret == EOK) {
+        tevent_req_done(req);
+    } else {
+        tevent_req_error(req, ret);
+    }
+}
+
+static errno_t sdap_access_ppolicy_recv(struct tevent_req *req)
+{
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    return EOK;
+}
+
 static errno_t sdap_get_basedn_user_entry(struct ldb_message *user_entry,
                                           const char *username,
                                           const char **_basedn)
diff --git a/src/providers/ldap/sdap_access.h b/src/providers/ldap/sdap_access.h
index f085e619961198b887d65ed5ee0bc5cdd90d1b20..7a2bf4de3d2cf3c5e5788404b84728304ea02349 100644
--- a/src/providers/ldap/sdap_access.h
+++ b/src/providers/ldap/sdap_access.h
@@ -35,6 +35,7 @@
 #define SYSDB_LDAP_ACCESS_CACHED_LOCKOUT "ldap_access_lockout_allow"
 /* names of ppolicy attributes */
 #define SYSDB_LDAP_ACCESS_LOCKED_TIME "pwdAccountLockedTime"
+#define SYSDB_LDAP_ACESS_LOCKOUT_DURATION "pwdLockoutDuration"
 #define SYSDB_LDAP_ACCESS_LOCKOUT "pwdLockout"
 
 #define LDAP_ACCESS_FILTER_NAME "filter"
@@ -42,6 +43,7 @@
 #define LDAP_ACCESS_SERVICE_NAME "authorized_service"
 #define LDAP_ACCESS_HOST_NAME "host"
 #define LDAP_ACCESS_LOCK_NAME "lockout"
+#define LDAP_ACCESS_PPOLICY_NAME "ppolicy"
 
 #define LDAP_ACCOUNT_EXPIRE_SHADOW "shadow"
 #define LDAP_ACCOUNT_EXPIRE_AD "ad"
@@ -57,6 +59,7 @@ enum ldap_access_rule {
     LDAP_ACCESS_SERVICE,
     LDAP_ACCESS_HOST,
     LDAP_ACCESS_LOCKOUT,
+    LDAP_ACCESS_PPOLICY,
     LDAP_ACCESS_LAST
 };
 
-- 
2.1.0

