On 03/03/2015 03:28 PM, Jakub Hrozek wrote:
On Mon, Mar 02, 2015 at 11:42:06PM +0100, Lukas Slebodnik wrote:
On (22/01/15 17:06), Pavel Reichl wrote:
Second patch now contains unit test.
>From f8686fbb988d29309a41064a678c0daf4aa40b6b Mon Sep 17 00:00:00 2001
From: Pavel Reichl <prei...@redhat.com>
Date: Mon, 19 Jan 2015 03:24:09 -0500
Subject: [PATCH 1/3] SDAP: new option - pwdlocking natural/only by admin

This is a follow up to #2364.
To distinguish user locked out from accessing machine via SSH if an
account was administratively locked (pwdAccountLockedTime set to
000001010000Z) in the OpenLDAP Password Policy overlay or if user
password is locked out from natural reasons (too many attempts, expired
password).

Part of solution for:
https://fedorahosted.org/sssd/ticket/2534
You wrote in description of this ticket that it it is a follow up to #2364.
from my point of view it is an extension.

Do we really need an option to enable this feature?

The current implementation locks account with pwdAccountLockedTime set to
000001010000Z. The other values(dates) are ignored.
I would prefer if we didn't add a new option as well, but since we released
a version that only supported the lockout and not any other semantics,
I don't think we can get away with just changing the functionality. A
minor version can break functionality. But a major version can :-)

So I propose the following:
1) Add a new value for ldap_access_order called "ppolicy" that would
evaluate the pwdAccountLockedTime fully, including the new
functionality in this patchset
2) In 1.12, deprecate the "lockout" option and log a warning that it
will be removed in future relase and users should migrate to "ppolicy"
option
3) In master (1.13), remove the "lockout" ldap_access_order value

That way, we won't add a new option and I think the "ppolicy" option is
better anyway.

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

New utility function *convert_time* to convert GeneralizedTime to
unix time.
According to the slapo-ppolicy man page, the pwdAccountLockedTime is
just generalizedTime and according to RFC 4517, it's possible to use any
timezone, but the UTC zone is preferred:
~~~~~~~~~~~~~~~~~~~~~
The time value represents coordinated universal time (equivalent to Greenwich
Mean Time) if the "Z" form of <g-time-zone> is used; otherwise, the value
represents a local time in the time zone indicated by <g-differential>.
In the latter case, coordinated universal time can be calculated by
subtracting the differential from the local time.  The "Z" form of
<g-time-zone> SHOULD be used in preference to <g-differential>.
~~~~~~~~~~~~~~~~~~~~~

I would suggest that the utility function would only support the "Z"
form and return a special error code if another timezone is specified.
In that case, the sssd access code would return ACCESS_DENIED and log a
failure saying that the timezone specifier in ppolicy is not supported.

The reason is that when it comes to access control, I strongly prefer
using non-ambiguous formats. We'll see if there are any users out there
who actually use a non-Zulu time in ppolicy and can improve the support
later.

Of course, the name of the function must make it clear that only time
specification with a trailing "Z" is supported, also the man page should
make it clear in description of "ppolicy" ldap_access_order that only "Z"
is supported.

---
Makefile.am            |  3 ++-
src/tests/util-tests.c | 47 +++++++++++++++++++++++++++++++++++++++++++++++
src/util/util.c        | 39 +++++++++++++++++++++++++++++++++++++++
src/util/util.h        |  3 +++
4 files changed, 91 insertions(+), 1 deletion(-)

diff --git a/Makefile.am b/Makefile.am
index 
ca8921bb132db6b7a57d9436a3219057d08b64c6..2da86572752d0702b8983f4cc647188c8cc81382
 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1535,7 +1535,8 @@ 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
It's better to link test with internal library (libsss_util.so) rather
then add source file to the test. It's not necessary to change it now.
Please try to ammend cmocka test in future. (test_utils)

It's not necessary to change it now. It's just info for the future.

BTW. You didn't add $(NULL) to the end of aotomake variable.

util_tests_CFLAGS = \
     $(AM_CFLAGS) \
     $(CHECK_CFLAGS)
diff --git a/src/tests/util-tests.c b/src/tests/util-tests.c
index 
94015d8e1e0efb143a4fea998f1b16db1e63365e..ebfae703ff7962c12b9eddf83d2c2e61db29b6eb
 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,44 @@ START_TEST(test_known_service)
}
END_TEST

+static void convert_time_tz(void)
Why do wee need a static wunction which does not accept any argument?
It's better to inline it or add argument timezone and change timezone(setenv)
inside function.

+{
+    errno_t ret;
+    time_t unix_time;
+
+    ret = convert_time("20140801115742Z", "%Y%m%d%H%M%SZ", &unix_time);
+    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;
+    const char *orig_tz;
+
+    ret = convert_time("0", format, &unix_time);
+    fail_unless(ret == EINVAL);
+    ret = convert_time("000001010000Z", format, &unix_time);
+    fail_unless(ret == EINVAL);
+
+    /* test that results are still same no matter what timezone is set */
+    convert_time_tz();
+    orig_tz = getenv("TZ");
+
+    ret = setenv("TZ", "GST-1", 1);
+    fail_if(ret == -1);
+    convert_time_tz();
+
+    ret = setenv("TZ", "GST-2", 1);
+    fail_if(ret == -1);
+    convert_time_tz();
+
+    ret = setenv("TZ", orig_tz, 1);
+    fail_if(ret == -1);
+}
+END_TEST
+
Suite *util_suite(void)
{
     Suite *s = suite_create("util");
@@ -1067,10 +1107,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,
                                ^
                                extra space

+                               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..eb3002fd4dde5b88a6595aee7186bae8d7d773d2
 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,40 @@ 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 convert_time(const char *str, const char *format, time_t *unix_time)
+{
+    char *end;
+    struct tm tm;
+
+    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;
+    }
+
+    *unix_time = mktime(&tm);
+    if (*unix_time == -1) {
+        DEBUG(SSSDBG_TRACE_INTERNAL,
+              "mktime failed to convert [%s].\n", str);
+        return EINVAL;
+    }
+
+    tzset();
+    *unix_time -= timezone;
   We prefer to touch output argument just once. (just set output argumetn with
   local variable.

   The 1st benefit is that function is not poluted with dereferencing of pointer
   (code is nicer). The second benefit is that you set default vale just once.
   It is not case in this function.


+    return EOK;
+}
diff --git a/src/util/util.h b/src/util/util.h
index 
60dbf9381c5fe26e7d42d51c6f9c7df6e3ebc4be..bc23d7bc5d6307f63a480c5faaeaa98ca921bd46
 100644
--- a/src/util/util.h
+++ b/src/util/util.h
@@ -641,4 +641,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 convert_time(const char *str, const char *format, time_t *unix_time);
  Please try to prefix this function. I know it's part of internal library,
  but the name is very general and in theory can conflic with function in other
  libraries/ (prefix sss_ or s3_ is fine)
Prefix and rename, please :-)

errno_t sss_utc_to_time_t() ?

btw do we need to have this function in src/util? Is it ever going to be
used anywhere else?


The output argumet shout be prefixed with underscore (_).
So it is obviout which pointer is input and which is output argument.


#endif /* __SSSD_UTIL_H__ */
--
2.1.0

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

Resolves:
https://fedorahosted.org/sssd/ticket/2534
---
Makefile.am                      |   3 +-
src/providers/ldap/sdap_access.c | 106 ++++++++++++++++++++++++++++++++++-----
src/providers/ldap/sdap_access.h |   1 +
3 files changed, 96 insertions(+), 14 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index 
2da86572752d0702b8983f4cc647188c8cc81382..e780b7098cc853dfaaa4ae4d830cc410fa94eb80
 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2383,7 +2383,8 @@ 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
The file src/util/util.c is part of internal library libsss_util.so
It's better to link this module with library rather then add it to source file.

libsss_ldap_common_la_CFLAGS = \
     $(KRB5_CFLAGS)
libsss_ldap_common_la_LIBADD = \
diff --git a/src/providers/ldap/sdap_access.c b/src/providers/ldap/sdap_access.c
index 
e09e6b8fe55e8e8bae9ff7f46e815595cb938971..39a6f4397f83b2ca560d0fb883002a97caf73c74
 100644
--- a/src/providers/ldap/sdap_access.c
+++ b/src/providers/ldap/sdap_access.c
@@ -1568,7 +1568,9 @@ errno_t sdap_access_lock_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 };
+    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);

@@ -1597,15 +1599,86 @@ done:
     return ret;
}

+static errno_t
+decide_if_account_is_locked(bool locked_only_by_admin,
+                            const char *pwdAccountLockedTime,
+                            const char *pwdAccountLockedDurationTime,
+                            bool *locked)
                               output argumet should be prefixed with "_"
Also is_account_locked() would be a shorter name :-)

+{
+    errno_t ret;
+    time_t lock_time;
+    time_t duration;
+    time_t now;
+
+    /* Default action is to consider account to be locked. */
+    *locked = true;
It's more readable if code does not touch output argument very often.
Functions is nicer if stars "*" are not used very often.


+
+    /* account is permanently locked */
+    if (strcasecmp(pwdAccountLockedTime,
+                   PERMANENTLY_LOCKED_ACCOUNT) == 0) {
+        goto done;
return variable ret is not initialized.

@see warnigs
Error: UNINIT (CWE-457): [#def1]
sssd-1.12.90/src/providers/ldap/sdap_access.c:1620: var_decl: Declaring variable 
"ret" without initializer.
sssd-1.12.90/src/providers/ldap/sdap_access.c:1680: uninit_use: Using uninitialized value 
"ret".

Error: CLANG_WARNING: [#def2]
sssd-1.12.90/src/providers/ldap/sdap_access.c:1680:5: warning: Undefined or 
garbage value returned to caller
#    return ret;
#    ^~~~~~~~~~

Error: COMPILER_WARNING: [#def4]
sssd-1.12.90/src/providers/ldap/sdap_access.c: scope_hint: In function 
'sdap_access_lock_step_done'
sssd-1.12.90/src/providers/ldap/sdap_access.c:1758:16: warning: 'ret' may be 
used uninitialized in this function [-Wmaybe-uninitialized]
#             if (ret != EOK) {
#

+    }
+
+    if (locked_only_by_admin) {
+        /* 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 is beeing blocked by password policy, "
+              "but value: [%s] value is ignored by SSSD.\n",
+              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 = convert_time(pwdAccountLockedTime, "%Y%m%d%H%M%SZ",
+                           &lock_time);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_TRACE_FUNC, "convert_time() 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 = strtol(pwdAccountLockedDurationTime, NULL, 0);
I think we already have a utility function for this in
src/util/strtonum.c, please use it.

+            if (errno) {
+                ret = errno;
+                goto done;
+            }
+            /* Lockout has expired */
+            if (duration != 0 && difftime(now, lock_time) > duration) {
+                *locked = false;
+            }
+        }
+    }
+
+    ret = EOK;
+
+done:
+    return ret;
+}
+
static void sdap_access_lock_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;
+    bool pwd_admin_locked_only;

     req = tevent_req_callback_data(subreq, struct tevent_req);
     state = tevent_req_data(req, struct sdap_access_lock_req_ctx);
@@ -1613,6 +1686,9 @@ static void sdap_access_lock_step_done(struct tevent_req 
*subreq)
     ret = sdap_get_generic_recv(subreq, state, &num_results, &results);
     talloc_zfree(subreq);

+    pwd_admin_locked_only = dp_opt_get_bool(state->opts->basic,
+                                            SDAP_PWDLOCKOUT_ADMIN_LOCKED_ONLY);
+
I have already commented it in 1st patch that IMHO we do not need this option
at all.

If other developers agree we need that option then please move it to the place
where we DO NEED it. Because it is used just once but initialized far far away
from place where it is used.

There is another warning from static analyser.
I think iit's false possitive because we needn't test output variable of
dp_opt_get_bool.


Error: CHECKED_RETURN (CWE-252): [#def3]
sssd-1.12.90/src/providers/ldap/sdap_access.c:1701: check_return: Calling 
"_dp_opt_get_bool" without checking return value (as is done elsewhere 48 out 
of 67 times).
sssd-1.12.90/src/providers/ad/ad_common.c:1170: example_checked: Example 1: 
"_dp_opt_get_bool(ad_ctx->ad_options->basic, AD_ENABLE_GC, <anonymous>)" has its value checked in 
"_dp_opt_get_bool(ad_ctx->ad_options->basic, AD_ENABLE_GC, <anonymous>)".
sssd-1.12.90/src/providers/ad/ad_dyndns.c:48: example_checked: Example 2: 
"_dp_opt_get_bool(ad_opts->dyndns_ctx->opts, DP_OPT_DYNDNS_UPDATE, <anonymous>)" has its value 
checked in "_dp_opt_get_bool(ad_opts->dyndns_ctx->opts, DP_OPT_DYNDNS_UPDATE, <anonymous>) == 0".
sssd-1.12.90/src/providers/ad/ad_id.c:230: example_checked: Example 3: 
"_dp_opt_get_bool(ad_ctx->ad_options->basic, AD_ENABLE_GC, <anonymous>)" has its value checked in 
"_dp_opt_get_bool(ad_ctx->ad_options->basic, AD_ENABLE_GC, <anonymous>)".
sssd-1.12.90/src/providers/ad/ad_init.c:235: example_checked: Example 4: "_dp_opt_get_bool(ad_options->basic, 
AD_ENABLE_DNS_SITES, <anonymous>)" has its value checked in "_dp_opt_get_bool(ad_options->basic, 
AD_ENABLE_DNS_SITES, <anonymous>)".
sssd-1.12.90/src/providers/ipa/ipa_init.c:332: example_checked: Example 5: 
"_dp_opt_get_bool(ipa_options->basic, IPA_ENABLE_DNS_SITES, <anonymous>)" has its value checked in 
"_dp_opt_get_bool(ipa_options->basic, IPA_ENABLE_DNS_SITES, <anonymous>)".
sssd-1.12.90/src/providers/ldap/sdap_access.c:1754: unchecked_value: Using value 
"pwd_admin_locked_only" as a function argument without checking appropriately.


     ret = sdap_id_op_done(state->sdap_op, ret, &dp_error);
     if (ret != EOK) {
         if (dp_error == DP_ERR_OK) {
@@ -1653,22 +1729,26 @@ 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 = decide_if_account_is_locked(pwd_admin_locked_only,
+                                              pwdAccountLockedTime,
+                                              pwdAccountLockedDurationTime,
+                                              &locked);
+            if (ret != EOK) {
+                DEBUG(SSSDBG_MINOR_FAILURE,
+                      "decide_if_account_is_locked failed: %d:[%s]. "
+                      "Account will be considered to be locked.\n",
+                      ret, sss_strerror(ret));
                 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
diff --git a/src/providers/ldap/sdap_access.h b/src/providers/ldap/sdap_access.h
index 
f085e619961198b887d65ed5ee0bc5cdd90d1b20..3e59b9b2f4851a87c01f3e353c11b3bdf9bb4ff7
 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"
--
2.1.0
Patch works as expexted.
Thank you for testing, I only read the patches, didn't do any testing
yet.

Tested with 3 different values of pwdAccountLockedTime

dn: uid=testuser1,ou=Users,dc=example,dc=com
changetype: modify
replace: pwdAccountLockedTime
pwdAccountLockedTime: 000001010000Z

dn: uid=testuser1,ou=Users,dc=example,dc=com
changetype: modify
replace: pwdAccountLockedTime
pwdAccountLockedTime: 20150310003750Z

dn: uid=testuser1,ou=Users,dc=example,dc=com
changetype: modify
replace: pwdAccountLockedTime
pwdAccountLockedTime: 20150301003750Z

LS
_______________________________________________
sssd-devel mailing list
sssd-devel@lists.fedorahosted.org
https://lists.fedorahosted.org/mailman/listinfo/sssd-devel
Thanks for comments, please see updated patchset.
>From 213f66c136fc2cabe79e1110afbcb7e29c330349 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 *convert_time* to convert GeneralizedTime to
unix time.
---
 Makefile.am            |  3 ++-
 src/tests/util-tests.c | 49 ++++++++++++++++++++++++++++++++++++++++++++++
 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, 109 insertions(+), 1 deletion(-)

diff --git a/Makefile.am b/Makefile.am
index ffbdf970a5702fcd745611b1ba739d31b4e98c0e..8d557b257c18b62c683e3007c820a1f93ff0595d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1554,7 +1554,8 @@ 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
 util_tests_CFLAGS = \
     $(AM_CFLAGS) \
     $(CHECK_CFLAGS)
diff --git a/src/tests/util-tests.c b/src/tests/util-tests.c
index 94015d8e1e0efb143a4fea998f1b16db1e63365e..bb7ace322f410d3a954306d6bf45772a8638e704 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,46 @@ START_TEST(test_known_service)
 }
 END_TEST
 
+static void convert_time_tz(const char* tz)
+{
+    errno_t ret;
+    time_t unix_time;
+
+    ret = setenv("TZ", tz, 1);
+    fail_if(ret == -1);
+
+    ret = sss_utc_to_time_t("20140801115742Z", "%Y%m%d%H%M%SZ", &unix_time);
+    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;
+    const char *orig_tz;
+
+    ret = sss_utc_to_time_t("20150127133540P", format, &unix_time);
+    fail_unless(ret == ERR_INVALID_LOCK_TIME);
+    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 */
+    orig_tz = getenv("TZ");
+
+    convert_time_tz(orig_tz);
+
+    convert_time_tz("GST-1");
+
+    convert_time_tz("GST-2");
+
+    ret = setenv("TZ", orig_tz, 1);
+    fail_if(ret == -1);
+}
+END_TEST
+
 Suite *util_suite(void)
 {
     Suite *s = suite_create("util");
@@ -1067,10 +1109,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..20cb473ae3f4e619a856118a3c02623d0ab0f6e1 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_INVALID_LOCK_TIME;
+    }
+
+    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..91f8c14e06701793d37d7586cf1099863edf4011 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 */
+    { "Invalid time of lockout" }, /* ERR_INVALID_LOCK_TIME */
 };
 
 
diff --git a/src/util/util_errors.h b/src/util/util_errors.h
index 39455dc8adfe8784bd3f06382d701b7f9e97f004..4337ee17a054431013f420c00d8cc0bb7870f093 100644
--- a/src/util/util_errors.h
+++ b/src/util/util_errors.h
@@ -87,6 +87,7 @@ enum sssd_errors {
     ERR_REFERRAL,
     ERR_SELINUX_CONTEXT,
     ERR_REGEX_NOMATCH,
+    ERR_INVALID_LOCK_TIME,
     ERR_LAST            /* ALWAYS LAST */
 };
 
-- 
2.1.0

>From ed81ba88e047a23160f3dc76306040eff7d59064 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                      |   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

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

Reply via email to