On 03/04/2015 10:38 PM, Jakub Hrozek wrote:
On Wed, Mar 04, 2015 at 09:45:19PM +0100, Pavel Reichl wrote:

On 03/04/2015 04:37 PM, Jakub Hrozek wrote:
On Wed, Mar 04, 2015 at 12:02:03PM +0100, Pavel Reichl wrote:
Patches needed to be rebased.
here are the fixups I mentioned in the other mail
Thanks, new patch set attached.
Thank you for the patience.
Thanks, for yours. Patch with duplication was no good.

Almost ack :-) The code now reads good to me, thanks for removing the
duplication. I couldn't figure out what's up with the tests, my brain is
too tired, we might need to ask nick what could be the issue.
hopefully fixed now:
ci:

http://sssd-ci.duckdns.org/logs/job/8/86/summary.html


 From a8826633a944e92b7248e446fd37c349c1c72292 Mon Sep 17 00:00:00 2001
From: Pavel Reichl <prei...@redhat.com>
Date: Tue, 20 Jan 2015 16:27:41 -0500
Subject: [PATCH 1/2] UTIL: convert GeneralizedTime to unix time
ack sans the test failure in CI. We need to solve it before pushing, but
the patch looks good to me.


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

Mostly good, I only have one place where we might not have
understood each other on IRC and one request.

If you are going to respin the patch once again, can you chance
strerror() for sss_strerror()? It's not a big deal since we'd see the
error value either way, it would just make the error output nicer since
we're using custom codes.
done

@@ -1462,28 +1493,30 @@ static void sdap_access_lock_connect_done(struct 
tevent_req *subreq)
      /* Connection to LDAP succeeded
       * Send 'pwdLockout' request
       */
-    ret = sdap_access_lock_get_lockout_step(req);
+    ret = sdap_access_ppolicy_get_lockout_step(req);
      if (ret != EOK && ret != EAGAIN) {
          DEBUG(SSSDBG_CRIT_FAILURE,
-              "sdap_access_lock_get_lockout_step failed: [%d][%s]\n",
+              "sdap_access_ppolicy_get_lockout_step failed: [%d][%s]\n",
                ret, strerror(ret));
-        tevent_req_error(req, ERR_ACCESS_DENIED);
+        tevent_req_error(req, ERR_INTERNAL);
          return;
      }
Here is the place where we might have not understand one another. The
code continues with either EOK or EAGAIN. I think it's correct to
continue with EAGAIN, but do you see issues with marking the request as
done if the function returns EOK? I think EOK should actually never
happen here, but we should handle it nonetheless, because the way I read
the code, the request would never finish otherwise.
done

btw -- this might be a good place to start using custom return codes
also in future patches. The code that iterates over search bases and
returns EAGAIN on search or EOK when it's done is quite common and I
wonder if using ERR_SEARCH_IN_PROGRESS and ERR_NO_MORE_SEARCH_BASES
would make the callers more readable. Just a thought.
nice but later

-static void sdap_access_lock_step_done(struct tevent_req *subreq)
+static errno_t
+is_account_locked(const char *pwdAccountLockedTime,
+                  const char *pwdAccountLockedDurationTime,
+                  enum sdap_pwpolicy_mode pwpol_mode,
+                  const char *username,
+                  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;
+    }
+
+    if (pwpol_mode == PWP_LOCKOUT_ONLY) {
+        /* We do *not* care about exact value of account locked time, we
+         * only *do* care if the value is equal to
+         * PERMANENTLY_LOCKED_ACCOUNT, which means that account is locked
+         * permanently.
+         */
+        DEBUG(SSSDBG_TRACE_FUNC,
+              "Account of: %s is beeing blocked by password policy, "
+              "but value: [%s] value is ignored by SSSD.\n",
+              username, pwdAccountLockedTime);
+        locked = false;
+    } else {
+        /* 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;
+            }
+        }
+    }
I would prefer a switch-case here, for one reason - we would need to
have all values of the enum and we would either get a compilation
warning if all values are not handled or an error if we use a default
handled. I prefer the first option, but IIRC Sumit prefers the second
and I'm not too sold on either of them.
done, but difficult to parse - please check

Thanks again for the patience. I know there's been several respins, but
this is access control code and should be reviewed really carefully.
OK, I agree, I just hate to do this things in time press, but I know it's my mistake.
_______________________________________________
sssd-devel mailing list
sssd-devel@lists.fedorahosted.org
https://lists.fedorahosted.org/mailman/listinfo/sssd-devel

>From 3d55ab9bb607ba6cae11199f9701d90aa326dcac Mon Sep 17 00:00:00 2001
From: Pavel Reichl <prei...@redhat.com>
Date: Tue, 20 Jan 2015 16:27:41 -0500
Subject: [PATCH 1/2] UTIL: convert GeneralizedTime to unix time

New utility function *sss_utc_to_time_t* to convert GeneralizedTime to
unix time.
---
 Makefile.am            | 10 ++++++---
 src/tests/util-tests.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++
 src/util/util.c        | 53 ++++++++++++++++++++++++++++++++++++++++++++++
 src/util/util.h        |  3 +++
 src/util/util_errors.c |  1 +
 src/util/util_errors.h |  1 +
 6 files changed, 122 insertions(+), 3 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index 0142b421755639adba8d8824aff2d5427b5c8b7c..6671c18e0d0b48422759846108819f84d985ef44 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1555,15 +1555,19 @@ simple_access_tests_LDADD = \
     libsss_test_common.la
 
 util_tests_SOURCES = \
-    src/tests/util-tests.c
+    src/tests/util-tests.c \
+    src/util/util.c \
+    $(NULL)
 util_tests_CFLAGS = \
     $(AM_CFLAGS) \
-    $(CHECK_CFLAGS)
+    $(CHECK_CFLAGS) \
+    $(NULL)
 util_tests_LDADD = \
     $(SSSD_LIBS) \
     $(CHECK_LIBS) \
     $(SSSD_INTERNAL_LTLIBS) \
-    libsss_test_common.la
+    libsss_test_common.la \
+    $(NULL)
 
 safe_format_tests_SOURCES = \
     src/tests/safe-format-tests.c
diff --git a/src/tests/util-tests.c b/src/tests/util-tests.c
index 94015d8e1e0efb143a4fea998f1b16db1e63365e..3829b2128a65e9fef35334510e462b53af93af48 100644
--- a/src/tests/util-tests.c
+++ b/src/tests/util-tests.c
@@ -28,6 +28,8 @@
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
+#include <stdlib.h>
+
 #include "util/util.h"
 #include "util/sss_utf8.h"
 #include "util/murmurhash3.h"
@@ -1020,6 +1022,54 @@ START_TEST(test_known_service)
 }
 END_TEST
 
+static void convert_time_tz(const char* tz)
+{
+    errno_t ret, ret2;
+    time_t unix_time;
+    const char *orig_tz = NULL;
+
+    orig_tz = getenv("TZ");
+    if (orig_tz == NULL) {
+        orig_tz = "";
+    }
+
+    if (tz) {
+        ret = setenv("TZ", tz, 1);
+        fail_if(ret == -1);
+    }
+
+    ret = sss_utc_to_time_t("20140801115742Z", "%Y%m%d%H%M%SZ", &unix_time);
+
+    /* restore */
+    if (orig_tz != NULL) {
+        ret2 = setenv("TZ", orig_tz, 1);
+        fail_if(ret2 == -1);
+    }
+    fail_unless(ret == EOK && difftime(1406894262, unix_time) == 0);
+}
+
+START_TEST(test_convert_time)
+{
+    const char *format = "%Y%m%d%H%M%SZ";
+    time_t unix_time;
+    errno_t ret;
+
+    ret = sss_utc_to_time_t("20150127133540P", format, &unix_time);
+    fail_unless(ret == ERR_TIMESPEC_NOT_SUPPORTED);
+    ret = sss_utc_to_time_t("0Z", format, &unix_time);
+    fail_unless(ret == EINVAL);
+    ret = sss_utc_to_time_t("000001010000Z", format, &unix_time);
+    fail_unless(ret == EINVAL);
+
+    /* test that results are still same no matter what timezone is set */
+    convert_time_tz(NULL);
+
+    convert_time_tz("GST-1");
+
+    convert_time_tz("GST-2");
+}
+END_TEST
+
 Suite *util_suite(void)
 {
     Suite *s = suite_create("util");
@@ -1067,10 +1117,17 @@ Suite *util_suite(void)
     tcase_add_test(tc_atomicio, test_atomicio_read_exact_sized_file);
     tcase_add_test(tc_atomicio, test_atomicio_read_from_empty_file);
 
+    TCase *tc_convert_time = tcase_create("convert_time");
+    tcase_add_checked_fixture(tc_convert_time,
+                              ck_leak_check_setup,
+                              ck_leak_check_teardown);
+    tcase_add_test(tc_convert_time, test_convert_time);
+
     suite_add_tcase (s, tc_util);
     suite_add_tcase (s, tc_utf8);
     suite_add_tcase (s, tc_mh3);
     suite_add_tcase (s, tc_atomicio);
+    suite_add_tcase (s, tc_convert_time);
 
     return s;
 }
diff --git a/src/util/util.c b/src/util/util.c
index 613c559bb2002686c7833642d0946e46e5a9b5d6..cfd26a58b31048996e9669163b821282b219b2de 100644
--- a/src/util/util.c
+++ b/src/util/util.c
@@ -18,6 +18,7 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+#include "config.h"
 #include <ctype.h>
 #include <netdb.h>
 #include <poll.h>
@@ -26,6 +27,7 @@
 #include <arpa/inet.h>
 #include <talloc.h>
 #include <dhash.h>
+#include <time.h>
 
 #include "util/util.h"
 #include "util/sss_utf8.h"
@@ -904,3 +906,54 @@ errno_t sss_fd_nonblocking(int fd)
 
     return EOK;
 }
+
+/* Convert GeneralizedTime (http://en.wikipedia.org/wiki/GeneralizedTime)
+ * to unix time (seconds since epoch). Use UTC time zone.
+ */
+errno_t sss_utc_to_time_t(const char *str, const char *format, time_t *_unix_time)
+{
+    char *end;
+    struct tm tm;
+    size_t len;
+    time_t ut;
+
+    if (str == NULL) {
+        return EINVAL;
+    }
+
+    len = strlen(str);
+    if (str[len-1] != 'Z') {
+        DEBUG(SSSDBG_TRACE_INTERNAL,
+              "%s does not seem to be in UTZ time zone.\n", str);
+        return ERR_TIMESPEC_NOT_SUPPORTED;
+    }
+
+    memset(&tm, 0, sizeof(tm));
+
+    end = strptime(str, format, &tm);
+    /* not all characters from format were matched */
+    if (end == NULL) {
+        DEBUG(SSSDBG_TRACE_INTERNAL,
+              "String [%s] failed to match format [%s].\n", str, format);
+        return EINVAL;
+    }
+
+    /* str is 'longer' than format */
+    if (*end != '\0') {
+        DEBUG(SSSDBG_TRACE_INTERNAL,
+              "String [%s] is longer than format [%s].\n", str, format);
+        return EINVAL;
+    }
+
+    ut = mktime(&tm);
+    if (ut == -1) {
+        DEBUG(SSSDBG_TRACE_INTERNAL,
+              "mktime failed to convert [%s].\n", str);
+        return EINVAL;
+    }
+
+    tzset();
+    ut -= timezone;
+    *_unix_time = ut;
+    return EOK;
+}
diff --git a/src/util/util.h b/src/util/util.h
index 22d6ef0a4e1340346d3d2997313aab50410f9dc0..829cf567ab24e907bf07082ae0a99e70e9668414 100644
--- a/src/util/util.h
+++ b/src/util/util.h
@@ -648,4 +648,7 @@ int set_seuser(const char *login_name, const char *seuser_name,
                const char *mlsrange);
 int del_seuser(const char *login_name);
 
+/* convert time from generalized form to unix time */
+errno_t sss_utc_to_time_t(const char *str, const char *format, time_t *unix_time);
+
 #endif /* __SSSD_UTIL_H__ */
diff --git a/src/util/util_errors.c b/src/util/util_errors.c
index 16d16fc777fc3344db8a3bdfeb3633bd5db48530..bfae5cd189902ed82ba8b7db29e85a309e4bd19c 100644
--- a/src/util/util_errors.c
+++ b/src/util/util_errors.c
@@ -65,6 +65,7 @@ struct err_string error_to_str[] = {
     { "LDAP search returned a referral" }, /* ERR_REFERRAL */
     { "Error setting SELinux user context" }, /* ERR_SELINUX_CONTEXT */
     { "Username format not allowed by re_expression" }, /* ERR_REGEX_NOMATCH */
+    { "Time specification not supported" }, /* ERR_TIMESPEC_NOT_SUPPORTED */
 };
 
 
diff --git a/src/util/util_errors.h b/src/util/util_errors.h
index 97e210e31dc6501860d1490966369a0d3ebe2cc2..069d4b78aa5ed6c756affdacab99c7141b7849e4 100644
--- a/src/util/util_errors.h
+++ b/src/util/util_errors.h
@@ -90,6 +90,7 @@ enum sssd_errors {
     ERR_REFERRAL,
     ERR_SELINUX_CONTEXT,
     ERR_REGEX_NOMATCH,
+    ERR_TIMESPEC_NOT_SUPPORTED,
     ERR_LAST            /* ALWAYS LAST */
 };
 
-- 
2.1.0

>From 10a7a667ba897fad62620b0fc981a0f87b7da956 Mon Sep 17 00:00:00 2001
From: Pavel Reichl <prei...@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                      |  14 +-
 src/man/sssd-ldap.5.xml          |  14 ++
 src/providers/ldap/ldap_init.c   |   2 +
 src/providers/ldap/sdap_access.c | 316 ++++++++++++++++++++++++++++-----------
 src/providers/ldap/sdap_access.h |   3 +
 5 files changed, 255 insertions(+), 94 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index 6671c18e0d0b48422759846108819f84d985ef44..95f7ed61b9716bb50f8533b3994c6d9a1ee03de0 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2459,16 +2459,22 @@ 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 \
+    $(NULL)
 libsss_ldap_common_la_CFLAGS = \
-    $(KRB5_CFLAGS)
+    $(KRB5_CFLAGS) \
+    $(NULL)
 libsss_ldap_common_la_LIBADD = \
     $(OPENLDAP_LIBS) \
     $(KRB5_LIBS) \
     libsss_krb5_common.la \
-    libsss_idmap.la
+    libsss_idmap.la \
+    libsss_util.la \
+    $(NULL)
 libsss_ldap_common_la_LDFLAGS = \
-    -avoid-version
+    -avoid-version \
+    $(NULL)
 
 if BUILD_SUDO
 libsss_ldap_common_la_SOURCES += \
diff --git a/src/man/sssd-ldap.5.xml b/src/man/sssd-ldap.5.xml
index dca9938b83efb33b592d1cd49206d9cc310fcf76..613b63f69c73161095b32db9bc16f66fe90ecff3 100644
--- a/src/man/sssd-ldap.5.xml
+++ b/src/man/sssd-ldap.5.xml
@@ -1955,6 +1955,20 @@ 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 represents any time in the past.
+                            The value of 'pwdAccountLockedTime' attribute
+                            must end with 'Z' as only UTC time zone 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 8d5619779d38c0df5ec4761b4409c71e8976686c..cebd548a4f787c2ddda56a1c5e74a60fa78d83ec 100644
--- a/src/providers/ldap/ldap_init.c
+++ b/src/providers/ldap/ldap_init.c
@@ -432,6 +432,8 @@ int sssm_ldap_access_init(struct be_ctx *bectx,
         } else if (strcasecmp(order_list[c],
                               LDAP_ACCESS_EXPIRE_POLICY_RENEW_NAME) == 0) {
             access_ctx->access_rule[c] = LDAP_ACCESS_EXPIRE_POLICY_RENEW;
+        } 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 3c8053cf70546b575b911b21501f4322b39654a1..f5feece5dcd6450756e8081cb71f190e11fabccb 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"
@@ -44,6 +45,12 @@
 #define PERMANENTLY_LOCKED_ACCOUNT "000001010000Z"
 #define MALFORMED_FILTER "Malformed access control filter [%s]\n"
 
+enum sdap_pwpolicy_mode {
+    PWP_LOCKOUT_ONLY,
+    PWP_LOCKOUT_EXPIRE,
+    PWP_SENTINEL,
+};
+
 static errno_t perform_pwexpire_policy(TALLOC_CTX *mem_ctx,
                                        struct sss_domain_info *domain,
                                        struct pam_data *pd,
@@ -59,14 +66,15 @@ static errno_t sdap_get_basedn_user_entry(struct ldb_message *user_entry,
                                           const char **_basedn);
 
 static struct tevent_req *
-sdap_access_lock_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);
+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,
+                         enum sdap_pwpolicy_mode pwpol_mod);
 
 static struct tevent_req *sdap_access_filter_send(TALLOC_CTX *mem_ctx,
                                              struct tevent_context *ev,
@@ -79,7 +87,7 @@ static struct tevent_req *sdap_access_filter_send(TALLOC_CTX *mem_ctx,
 
 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,
@@ -205,14 +213,34 @@ static errno_t sdap_access_check_next_rule(struct sdap_access_req_ctx *state,
             return EOK;
 
         case LDAP_ACCESS_LOCKOUT:
-            subreq = sdap_access_lock_send(state, state->ev, state->be_ctx,
-                                           state->domain,
-                                           state->access_ctx,
-                                           state->conn,
-                                           state->pd->user,
-                                           state->user_entry);
+            subreq = sdap_access_ppolicy_send(state, state->ev, state->be_ctx,
+                                              state->domain,
+                                              state->access_ctx,
+                                              state->conn,
+                                              state->pd->user,
+                                              state->user_entry,
+                                              PWP_LOCKOUT_ONLY);
             if (subreq == NULL) {
-                DEBUG(SSSDBG_CRIT_FAILURE, "sdap_access_lock_send failed.\n");
+                DEBUG(SSSDBG_CRIT_FAILURE, "sdap_access_ppolicy_send failed.\n");
+                return ENOMEM;
+            }
+
+            state->ac_type = SDAP_ACCESS_CONTROL_PPOLICY_LOCK;
+
+            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,
+                                              PWP_LOCKOUT_EXPIRE);
+            if (subreq == NULL) {
+                DEBUG(SSSDBG_CRIT_FAILURE,
+                      "sdap_access_ppolicy_send failed.\n");
                 return ENOMEM;
             }
 
@@ -302,7 +330,7 @@ static void sdap_access_done(struct tevent_req *subreq)
         ret = sdap_access_filter_recv(subreq);
         break;
     case SDAP_ACCESS_CONTROL_PPOLICY_LOCK:
-        ret = sdap_access_lock_recv(subreq);
+        ret = sdap_access_ppolicy_recv(subreq);
         break;
     default:
         ret = EINVAL;
@@ -791,8 +819,8 @@ struct sdap_access_filter_req_ctx {
 
 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,
@@ -1267,12 +1295,12 @@ static errno_t sdap_access_host(struct ldb_message *user_entry)
     return ret;
 }
 
-static void sdap_access_lock_get_lockout_done(struct tevent_req *subreq);
-static int sdap_access_lock_retry(struct tevent_req *req);
-static errno_t sdap_access_lock_step(struct tevent_req *req);
-static void sdap_access_lock_step_done(struct tevent_req *subreq);
+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);
 
-struct sdap_access_lock_req_ctx {
+struct sdap_access_ppolicy_req_ctx {
     const char *username;
     const char *filter;
     struct tevent_context *ev;
@@ -1288,24 +1316,26 @@ struct sdap_access_lock_req_ctx {
     /* default DNs to ppolicy */
     const char **ppolicy_dns;
     unsigned int ppolicy_dns_index;
+    enum sdap_pwpolicy_mode pwpol_mode;
 };
 
 static struct tevent_req *
-sdap_access_lock_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)
+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,
+                         enum sdap_pwpolicy_mode pwpol_mode)
 {
-    struct sdap_access_lock_req_ctx *state;
+    struct sdap_access_ppolicy_req_ctx *state;
     struct tevent_req *req;
     errno_t ret;
 
     req = tevent_req_create(mem_ctx,
-                            &state, struct sdap_access_lock_req_ctx);
+                            &state, struct sdap_access_ppolicy_req_ctx);
     if (req == NULL) {
         return NULL;
     }
@@ -1318,9 +1348,10 @@ sdap_access_lock_send(TALLOC_CTX *mem_ctx,
     state->access_ctx = access_ctx;
     state->domain = domain;
     state->ppolicy_dns_index = 0;
+    state->pwpol_mode = pwpol_mode;
 
     DEBUG(SSSDBG_TRACE_FUNC,
-          "Performing access lock check for user [%s]\n", username);
+          "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);
@@ -1338,7 +1369,7 @@ sdap_access_lock_send(TALLOC_CTX *mem_ctx,
         goto done;
     }
 
-    DEBUG(SSSDBG_TRACE_FUNC, "Checking lock against LDAP\n");
+    DEBUG(SSSDBG_TRACE_FUNC, "Checking ppolicy against LDAP\n");
 
     state->sdap_op = sdap_id_op_create(state,
                                        state->conn->conn_cache);
@@ -1348,7 +1379,7 @@ sdap_access_lock_send(TALLOC_CTX *mem_ctx,
         goto done;
     }
 
-    ret = sdap_access_lock_retry(req);
+    ret = sdap_access_ppolicy_retry(req);
     if (ret != EOK) {
         goto done;
     }
@@ -1365,21 +1396,22 @@ done:
     return req;
 }
 
-static int sdap_access_lock_retry(struct tevent_req *req)
+static int sdap_access_ppolicy_retry(struct tevent_req *req)
 {
-    struct sdap_access_lock_req_ctx *state;
+    struct sdap_access_ppolicy_req_ctx *state;
     struct tevent_req *subreq;
     int ret;
 
-    state = tevent_req_data(req, struct sdap_access_lock_req_ctx);
+    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));
+              "sdap_id_op_connect_send failed: %d (%s)\n",
+              ret, sss_strerror(ret));
         return ret;
     }
 
-    tevent_req_set_callback(subreq, sdap_access_lock_connect_done, req);
+    tevent_req_set_callback(subreq, sdap_access_ppolicy_connect_done, req);
     return EOK;
 }
 
@@ -1406,15 +1438,15 @@ get_default_ppolicy_dns(TALLOC_CTX *mem_ctx, struct sdap_domain *sdom)
     return ppolicy_dns;
 }
 
-static void sdap_access_lock_connect_done(struct tevent_req *subreq)
+static void sdap_access_ppolicy_connect_done(struct tevent_req *subreq)
 {
     struct tevent_req *req;
-    struct sdap_access_lock_req_ctx *state;
+    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_lock_req_ctx);
+    state = tevent_req_data(req, struct sdap_access_ppolicy_req_ctx);
 
     ret = sdap_id_op_connect_recv(subreq, &dp_error);
     talloc_zfree(subreq);
@@ -1440,7 +1472,7 @@ static void sdap_access_lock_connect_done(struct tevent_req *subreq)
         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);
+            tevent_req_error(req, ERR_INTERNAL);
             return;
         }
 
@@ -1454,7 +1486,7 @@ static void sdap_access_lock_connect_done(struct tevent_req *subreq)
 
         state->ppolicy_dns = get_default_ppolicy_dns(state, state->opts->sdom);
         if (state->ppolicy_dns == NULL) {
-            tevent_req_error(req, ERR_ACCESS_DENIED);
+            tevent_req_error(req, ERR_INTERNAL);
             return;
         }
     }
@@ -1462,28 +1494,33 @@ static void sdap_access_lock_connect_done(struct tevent_req *subreq)
     /* Connection to LDAP succeeded
      * Send 'pwdLockout' request
      */
-    ret = sdap_access_lock_get_lockout_step(req);
+    ret = sdap_access_ppolicy_get_lockout_step(req);
     if (ret != EOK && ret != EAGAIN) {
         DEBUG(SSSDBG_CRIT_FAILURE,
-              "sdap_access_lock_get_lockout_step failed: [%d][%s]\n",
-              ret, strerror(ret));
-        tevent_req_error(req, ERR_ACCESS_DENIED);
+              "sdap_access_ppolicy_get_lockout_step failed: [%d][%s]\n",
+              ret, sss_strerror(ret));
+        tevent_req_error(req, ERR_INTERNAL);
         return;
     }
+
+    if (ret == EOK) {
+        tevent_req_done(req);
+    }
 }
 
 static errno_t
-sdap_access_lock_get_lockout_step(struct tevent_req *req)
+sdap_access_ppolicy_get_lockout_step(struct tevent_req *req)
 {
     const char *attrs[] = { SYSDB_LDAP_ACCESS_LOCKOUT, NULL };
-    struct sdap_access_lock_req_ctx *state;
+    struct sdap_access_ppolicy_req_ctx *state;
     struct tevent_req *subreq;
     errno_t ret;
 
-    state = tevent_req_data(req, struct sdap_access_lock_req_ctx);
+    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) {
+        DEBUG(SSSDBG_TRACE_FUNC, "No more DNs to try.\n");
         ret = EOK;
         goto done;
     }
@@ -1505,14 +1542,13 @@ sdap_access_lock_get_lockout_step(struct tevent_req *req)
                                    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_lock_get_lockout_done, req);
+    tevent_req_set_callback(subreq, sdap_access_ppolicy_get_lockout_done, req);
 
     ret = EAGAIN;
 
@@ -1520,17 +1556,17 @@ done:
     return ret;
 }
 
-static void sdap_access_lock_get_lockout_done(struct tevent_req *subreq)
+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_lock_req_ctx *state;
+    struct sdap_access_ppolicy_req_ctx *state;
 
     req = tevent_req_callback_data(subreq, struct tevent_req);
-    state = tevent_req_data(req, struct sdap_access_lock_req_ctx);
+    state = tevent_req_data(req, struct sdap_access_ppolicy_req_ctx);
 
     ret = sdap_get_generic_recv(subreq, state, &num_results, &results);
     talloc_zfree(subreq);
@@ -1548,7 +1584,7 @@ static void sdap_access_lock_get_lockout_done(struct tevent_req *subreq)
     /* Didn't find ppolicy attribute */
     if (num_results < 1) {
         /* Try using next $search_base */
-        ret = sdap_access_lock_get_lockout_step(req);
+        ret = sdap_access_ppolicy_get_lockout_step(req);
         if (ret == EOK) {
             /* No more search bases to try */
             DEBUG(SSSDBG_CONF_SETTINGS,
@@ -1557,8 +1593,9 @@ static void sdap_access_lock_get_lockout_done(struct tevent_req *subreq)
         } else {
             if (ret != EAGAIN) {
                 DEBUG(SSSDBG_CRIT_FAILURE,
-                      "sdap_access_lock_get_lockout_step failed: [%d][%s]\n",
-                      ret, strerror(ret));
+                      "sdap_access_ppolicy_get_lockout_step failed: "
+                      "[%d][%s]\n",
+                      ret, sss_strerror(ret));
             }
             goto done;
         }
@@ -1579,7 +1616,7 @@ static void sdap_access_lock_get_lockout_done(struct tevent_req *subreq)
         if (ret != EOK) {
             DEBUG(SSSDBG_MINOR_FAILURE,
                   "Error reading %s: [%s]\n", SYSDB_LDAP_ACCESS_LOCKOUT,
-                  strerror(ret));
+                  sss_strerror(ret));
             ret = ERR_INTERNAL;
             goto done;
         }
@@ -1590,11 +1627,11 @@ static void sdap_access_lock_get_lockout_done(struct tevent_req *subreq)
               "Password policy is enabled on LDAP server.\n");
 
         /* ppolicy is enabled => find out if account is locked */
-        ret = sdap_access_lock_step(req);
+        ret = sdap_access_ppolicy_step(req);
         if (ret != EOK && ret != EAGAIN) {
             DEBUG(SSSDBG_CRIT_FAILURE,
-                  "sdap_access_lock_step failed: [%d][%s].\n",
-                  ret, strerror(ret));
+                  "sdap_access_ppolicy_step failed: [%d][%s].\n",
+                  ret, sss_strerror(ret));
         }
         goto done;
     } else {
@@ -1635,14 +1672,16 @@ done:
     }
 }
 
-errno_t sdap_access_lock_step(struct tevent_req *req)
+errno_t sdap_access_ppolicy_step(struct tevent_req *req)
 {
     errno_t ret;
     struct tevent_req *subreq;
-    struct sdap_access_lock_req_ctx *state;
-    const char *attrs[] = { SYSDB_LDAP_ACCESS_LOCKED_TIME, NULL };
+    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_lock_req_ctx);
+    state = tevent_req_data(req, struct sdap_access_ppolicy_req_ctx);
 
     subreq = sdap_get_generic_send(state,
                                    state->ev,
@@ -1657,30 +1696,114 @@ errno_t sdap_access_lock_step(struct tevent_req *req)
                                    false);
 
     if (subreq == NULL) {
-        DEBUG(SSSDBG_CRIT_FAILURE, "sdap_access_lock_send failed.\n");
+        DEBUG(SSSDBG_CRIT_FAILURE, "sdap_access_ppolicy_send failed.\n");
         ret = ENOMEM;
         goto done;
     }
 
-    tevent_req_set_callback(subreq, sdap_access_lock_step_done, req);
+    tevent_req_set_callback(subreq, sdap_access_ppolicy_step_done, req);
     ret = EAGAIN;
 
 done:
     return ret;
 }
 
-static void sdap_access_lock_step_done(struct tevent_req *subreq)
+static errno_t
+is_account_locked(const char *pwdAccountLockedTime,
+                  const char *pwdAccountLockedDurationTime,
+                  enum sdap_pwpolicy_mode pwpol_mode,
+                  const char *username,
+                  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;
+    }
+
+    switch(pwpol_mode) {
+    case PWP_LOCKOUT_ONLY:
+        /* We do *not* care about exact value of account locked time, we
+         * only *do* care if the value is equal to
+         * PERMANENTLY_LOCKED_ACCOUNT, which means that account is locked
+         * permanently.
+         */
+        DEBUG(SSSDBG_TRACE_FUNC,
+              "Account of: %s is beeing blocked by password policy, "
+              "but value: [%s] value is ignored by SSSD.\n",
+              username, pwdAccountLockedTime);
+        locked = false;
+        break;
+    case PWP_LOCKOUT_EXPIRE:
+        /* 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;
+            }
+        }
+        break;
+    case PWP_SENTINEL:
+    default:
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              "Unexpected value of password policy mode: %d.\n", pwpol_mode);
+    }
+
+    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_lock_req_ctx *state;
+    struct sdap_access_ppolicy_req_ctx *state;
 
     req = tevent_req_callback_data(subreq, struct tevent_req);
-    state = tevent_req_data(req, struct sdap_access_lock_req_ctx);
+    state = tevent_req_data(req, struct sdap_access_ppolicy_req_ctx);
 
     ret = sdap_get_generic_recv(subreq, state, &num_results, &results);
     talloc_zfree(subreq);
@@ -1689,7 +1812,7 @@ static void sdap_access_lock_step_done(struct tevent_req *subreq)
     if (ret != EOK) {
         if (dp_error == DP_ERR_OK) {
             /* retry */
-            tret = sdap_access_lock_retry(req);
+            tret = sdap_access_ppolicy_retry(req);
             if (tret == EOK) {
                 return;
             }
@@ -1712,7 +1835,7 @@ static void sdap_access_lock_step_done(struct tevent_req *subreq)
     if (num_results < 1) {
         DEBUG(SSSDBG_CONF_SETTINGS,
               "User [%s] was not found with the specified filter. "
-                  "Denying access.\n", state->username);
+              "Denying access.\n", state->username);
     } else if (results == NULL) {
         DEBUG(SSSDBG_CRIT_FAILURE, "num_results > 0, but results is NULL\n");
         ret = ERR_INTERNAL;
@@ -1725,22 +1848,35 @@ static void sdap_access_lock_step_done(struct tevent_req *subreq)
         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) {
-            /* We do *not* care about exact value of account locked time, we
-             * only *do* care if the value is equal to
-             * PERMANENTLY_LOCKED_ACCOUNT, which means that account is locked
-             * permanently.
-             */
-            if (strcasecmp(pwdAccountLockedTime,
-                           PERMANENTLY_LOCKED_ACCOUNT) == 0) {
+
+            ret = is_account_locked(pwdAccountLockedTime,
+                                    pwdAccountLockedDurationTime,
+                                    state->pwpol_mode,
+                                    state->username,
+                                    &locked);
+            if (ret != EOK) {
+                if (ret == ERR_TIMESPEC_NOT_SUPPORTED) {
+                    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 {
-                DEBUG(SSSDBG_TRACE_FUNC,
-                      "Account of: %s is beeing blocked by password policy, "
-                      "but value: [%s] value is ignored by SSSD.\n",
-                      state->username, pwdAccountLockedTime);
             }
         } else {
             /* Attribute SYSDB_LDAP_ACCESS_LOCKED_TIME in not be present unless
@@ -1786,7 +1922,7 @@ done:
     }
 }
 
-static errno_t sdap_access_lock_recv(struct tevent_req *req)
+static errno_t sdap_access_ppolicy_recv(struct tevent_req *req)
 {
     TEVENT_REQ_RETURN_ON_ERROR(req);
 
diff --git a/src/providers/ldap/sdap_access.h b/src/providers/ldap/sdap_access.h
index a8c6639109bd7e6dcb325a5e8d080f743ec56d97..6e637be5653a71415b917d115a61eaa0b6ccea9a 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"
@@ -45,6 +46,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"
@@ -63,6 +65,7 @@ enum ldap_access_rule {
     LDAP_ACCESS_EXPIRE_POLICY_REJECT,
     LDAP_ACCESS_EXPIRE_POLICY_WARN,
     LDAP_ACCESS_EXPIRE_POLICY_RENEW,
+    LDAP_ACCESS_PPOLICY,
     LDAP_ACCESS_LAST
 };
 
-- 
2.1.0

_______________________________________________
sssd-devel mailing list
sssd-devel@lists.fedorahosted.org
https://lists.fedorahosted.org/mailman/listinfo/sssd-devel

Reply via email to