[SSSD] [PATCH] Add new account expired rule to LDAP access provider

Sumit Bose sbose at redhat.com
Fri Dec 3 11:56:36 UTC 2010


On Thu, Dec 02, 2010 at 09:00:37AM -0500, Stephen Gallagher wrote:
> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
> 
> On 11/25/2010 08:25 AM, Sumit Bose wrote:
> > Hi,
> > 
> > these two patches aim to fix trac ticket #672. While the first patch
> > only makes a utility function public the second adds some new layers to
> > the LDAP access provider. I've tried to make the changes in a way to
> > make it easy to add new rules (#670) and new expire policies (#673,
> > #674, #690).
> > 
> > I would like to ask the reviewers to check if the new code is really as
> > flexible as I think and if it enough to evaluate the shadow expire
> > attribute here. TIA
> > 
> > bye,
> > Sumit
> > 
> 
> 
> Patch 0001: Ack.
> 
> Patch 0002: Nack.
> The manpage entry for ldap_account_expire_policy should list the valid
> values for this option. The patch comment notes that this is only
> "shadow" at present.

done

> 
> sssm_ldap_access_init() should throw an error (or at least warn and
> ignore it) if we get a duplicate access rule. e.g.:
> ldap_access_order = filter, filter

I prefer the error, because it might not be clear which order was
intended.

> 
> Please rename sdap_access_decide_offline(), sdap_access_retry(),
> sdap_access_connect_done() and sdap_access_get_access_done() to be clear
> that they apply to the access_filter.

done

> 
> I'm not sure we want to deny on a missing password expiration attribute.
> Some users might be lacking this attribute with the intent that it means
> "no expiration". Maybe we should add a strictness option here.

I checked you pam_ldap handles this. A missing attribute or a value of 0
means "no expiration".

> 
> next_access_rule() should probably return on PAM_ACCT_EXPIRED, not just
> PAM_PERM_DENIED.

done

Thanks for the review, new version attached.

bye,
Sumit

> 
> 
> - -- 
> Stephen Gallagher
> RHCE 804006346421761
> 
> Delivering value year after year.
> Red Hat ranks #1 in value among software vendors.
> http://www.redhat.com/promo/vendor/
> -----BEGIN PGP SIGNATURE-----
> Version: GnuPG v1.4.11 (GNU/Linux)
> Comment: Using GnuPG with Fedora - http://enigmail.mozdev.org/
> 
> iEYEARECAAYFAkz3poUACgkQeiVVYja6o6OzvQCfesHt9x72o974xPOquRY0NtKt
> 1VAAn2/yojzemyFBhnv9Zbm3HLQk6rr1
> =SinO
> -----END PGP SIGNATURE-----
> _______________________________________________
> sssd-devel mailing list
> sssd-devel at lists.fedorahosted.org
> https://fedorahosted.org/mailman/listinfo/sssd-devel
-------------- next part --------------
From 7d6c303951ccc0eae7dbb79c334a6bf7d99e5276 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Tue, 23 Nov 2010 17:43:23 +0100
Subject: [PATCH 1/2] Make string_to_shadowpw_days() public

---
 src/providers/ldap/ldap_auth.c   |   33 ---------------------------------
 src/providers/ldap/ldap_common.c |   34 ++++++++++++++++++++++++++++++++++
 src/providers/ldap/ldap_common.h |    2 ++
 3 files changed, 36 insertions(+), 33 deletions(-)

diff --git a/src/providers/ldap/ldap_auth.c b/src/providers/ldap/ldap_auth.c
index 57a549d..5a85fe9 100644
--- a/src/providers/ldap/ldap_auth.c
+++ b/src/providers/ldap/ldap_auth.c
@@ -232,39 +232,6 @@ static errno_t check_pwexpire_ldap(struct pam_data *pd,
     return EOK;
 }
 
-static errno_t string_to_shadowpw_days(const char *s, long *d)
-{
-    long l;
-    char *endptr;
-
-    if (s == NULL || *s == '\0') {
-        *d = -1;
-        return EOK;
-    }
-
-    errno = 0;
-    l = strtol(s, &endptr, 10);
-    if (errno != 0) {
-        DEBUG(1, ("strtol failed [%d][%s].\n", errno, strerror(errno)));
-        return errno;
-    }
-
-    if (*endptr != '\0') {
-        DEBUG(1, ("Input string [%s] is invalid.\n", s));
-        return EINVAL;
-    }
-
-    if (*d < -1) {
-        DEBUG(1, ("Input string contains not allowed negative value [%d].\n",
-                  *d));
-        return EINVAL;
-    }
-
-    *d = l;
-
-    return EOK;
-}
-
 static errno_t find_password_expiration_attributes(TALLOC_CTX *mem_ctx,
                                                const struct ldb_message *msg,
                                                struct dp_option *opts,
diff --git a/src/providers/ldap/ldap_common.c b/src/providers/ldap/ldap_common.c
index aa0bac6..d9d44c8 100644
--- a/src/providers/ldap/ldap_common.c
+++ b/src/providers/ldap/ldap_common.c
@@ -738,3 +738,37 @@ done:
     talloc_zfree(tmp_ctx);
     return ret;
 }
+
+errno_t string_to_shadowpw_days(const char *s, long *d)
+{
+    long l;
+    char *endptr;
+
+    if (s == NULL || *s == '\0') {
+        *d = -1;
+        return EOK;
+    }
+
+    errno = 0;
+    l = strtol(s, &endptr, 10);
+    if (errno != 0) {
+        DEBUG(1, ("strtol failed [%d][%s].\n", errno, strerror(errno)));
+        return errno;
+    }
+
+    if (*endptr != '\0') {
+        DEBUG(1, ("Input string [%s] is invalid.\n", s));
+        return EINVAL;
+    }
+
+    if (*d < -1) {
+        DEBUG(1, ("Input string contains not allowed negative value [%d].\n",
+                  *d));
+        return EINVAL;
+    }
+
+    *d = l;
+
+    return EOK;
+}
+
diff --git a/src/providers/ldap/ldap_common.h b/src/providers/ldap/ldap_common.h
index 5c298d1..d24a983 100644
--- a/src/providers/ldap/ldap_common.h
+++ b/src/providers/ldap/ldap_common.h
@@ -143,4 +143,6 @@ int netgroup_get_recv(struct tevent_req *req, int *dp_error_out);
 /* setup child logging */
 int setup_child(struct sdap_id_ctx *ctx);
 
+
+errno_t string_to_shadowpw_days(const char *s, long *d);
 #endif /* _LDAP_COMMON_H_ */
-- 
1.7.3.2

-------------- next part --------------
From 2d0dea8d99219bd5a28201be8022f670e675e893 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Mon, 22 Nov 2010 14:24:23 +0100
Subject: [PATCH 2/2] Add new account expired rule to LDAP access provider

Two new options are added to the LDAP access provider to allow a broader
range of access control rules to be evaluated.

'ldap_access_order' makes it possible to run more than one rule. To keep
compatibility with older versions the default is 'filter'. This patch
adds a new rule 'expire'.

'ldap_account_expire_policy' specifies which LDAP attribute should be
used to determine if an account is expired or not. Currently only
'shadow' is supported which evaluates the ldap_user_shadow_expire
attribute.
---
 src/config/SSSDConfig.py                 |    2 +
 src/config/etc/sssd.api.d/sssd-ldap.conf |    2 +
 src/man/sssd-ldap.5.xml                  |   55 ++++-
 src/providers/ipa/ipa_common.c           |    4 +-
 src/providers/ipa/ipa_common.h           |    2 +-
 src/providers/ldap/ldap_common.c         |    4 +-
 src/providers/ldap/ldap_init.c           |  109 ++++++--
 src/providers/ldap/sdap.h                |    2 +
 src/providers/ldap/sdap_access.c         |  443 +++++++++++++++++++++++++-----
 src/providers/ldap/sdap_access.h         |   15 +-
 src/util/util.c                          |   24 ++
 src/util/util.h                          |    2 +
 12 files changed, 571 insertions(+), 93 deletions(-)

diff --git a/src/config/SSSDConfig.py b/src/config/SSSDConfig.py
index 7da1027..7136e76 100644
--- a/src/config/SSSDConfig.py
+++ b/src/config/SSSDConfig.py
@@ -161,6 +161,8 @@ option_strings = {
 
     # [provider/ldap/access]
     'ldap_access_filter' : _('LDAP filter to determine access privileges'),
+    'ldap_account_expire_policy' : _('Which attributes shall be used to evaluate if an account is expired'),
+    'ldap_access_order' : _('Which rules should be used to evaluate access control'),
 
     # [provider/simple/access]
     'simple_allow_users' : _('Comma separated list of allowed users'),
diff --git a/src/config/etc/sssd.api.d/sssd-ldap.conf b/src/config/etc/sssd.api.d/sssd-ldap.conf
index 404f4d5..b7d2f9b 100644
--- a/src/config/etc/sssd.api.d/sssd-ldap.conf
+++ b/src/config/etc/sssd.api.d/sssd-ldap.conf
@@ -80,6 +80,8 @@ ldap_pwd_policy = str, None, false
 
 [provider/ldap/access]
 ldap_access_filter = str, None, false
+ldap_account_expire_policy = str, None, false
+ldap_access_order = str, None, false
 
 [provider/ldap/chpass]
 
diff --git a/src/man/sssd-ldap.5.xml b/src/man/sssd-ldap.5.xml
index cf6747e..916acc6 100644
--- a/src/man/sssd-ldap.5.xml
+++ b/src/man/sssd-ldap.5.xml
@@ -370,7 +370,8 @@
                     <term>ldap_user_shadow_expire (string)</term>
                     <listitem>
                         <para>
-                            When using ldap_pwd_policy=shadow, this parameter
+                            When using ldap_pwd_policy=shadow or
+                            ldap_account_expire_policy=shadow, this parameter
                             contains the name of an LDAP attribute corresponding
                             to its
                             <citerefentry>
@@ -1026,6 +1027,58 @@ ldap_access_filter = memberOf=cn=allowedusers,ou=Groups,dc=example,dc=com
                 </varlistentry>
 
                 <varlistentry>
+                    <term>ldap_account_expire_policy (string)</term>
+                    <listitem>
+                        <para>
+                            With this option a client side evaluation of
+                            access control attributes can be enabled.
+                        </para>
+                        <para>
+                            Please note that it is always recommended to
+                            use server side access control, i.e. the LDAP
+                            server should deny the bind request with a
+                            suitable error code even if the password is
+                            correct.
+                        </para>
+                        <para>
+                            The following values are allowed:
+                        </para>
+                        <para>
+                            <emphasis>shadow</emphasis>: use the value of
+                            ldap_user_shadow_expire to determine if the account
+                            is expired.
+                        </para>
+                        <para>
+                            Default: Empty
+                        </para>
+                    </listitem>
+                </varlistentry>
+
+                <varlistentry>
+                    <term>ldap_access_order (string)</term>
+                    <listitem>
+                        <para>
+                            Comma separated list of access control options.
+                            Allowed values are:
+                        </para>
+                        <para>
+                            <emphasis>filter</emphasis>: use ldap_access_filter
+                        </para>
+                        <para>
+                            <emphasis>expire</emphasis>: use
+                            ldap_account_expire_policy
+                        </para>
+                        <para>
+                            Default: filter
+                        </para>
+                        <para>
+                             Please note tat it is a configuration error if a
+                             value is used more than once.
+                        </para>
+                    </listitem>
+                </varlistentry>
+
+                <varlistentry>
                     <term>ldap_deref (string)</term>
                     <listitem>
                         <para>
diff --git a/src/providers/ipa/ipa_common.c b/src/providers/ipa/ipa_common.c
index 9eaa011..9719ab9 100644
--- a/src/providers/ipa/ipa_common.c
+++ b/src/providers/ipa/ipa_common.c
@@ -77,7 +77,9 @@ struct dp_option ipa_def_ldap_opts[] = {
     { "ldap_access_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING },
     { "ldap_netgroup_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING },
     { "ldap_group_nesting_level", DP_OPT_NUMBER, { .number = 2 }, NULL_NUMBER },
-    { "ldap_deref", DP_OPT_STRING, NULL_STRING, NULL_STRING }
+    { "ldap_deref", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ldap_account_expire_policy", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ldap_access_order", DP_OPT_STRING, NULL_STRING, NULL_STRING }
 };
 
 struct sdap_attr_map ipa_attr_map[] = {
diff --git a/src/providers/ipa/ipa_common.h b/src/providers/ipa/ipa_common.h
index ba8f85e..d457d7f 100644
--- a/src/providers/ipa/ipa_common.h
+++ b/src/providers/ipa/ipa_common.h
@@ -35,7 +35,7 @@ struct ipa_service {
 /* the following defines are used to keep track of the options in the ldap
  * module, so that if they change and ipa is not updated correspondingly
  * this will trigger a runtime abort error */
-#define IPA_OPTS_BASIC_TEST 39
+#define IPA_OPTS_BASIC_TEST 41
 
 /* the following define is used to keep track of the options in the krb5
  * module, so that if they change and ipa is not updated correspondingly
diff --git a/src/providers/ldap/ldap_common.c b/src/providers/ldap/ldap_common.c
index d9d44c8..dc01226 100644
--- a/src/providers/ldap/ldap_common.c
+++ b/src/providers/ldap/ldap_common.c
@@ -72,7 +72,9 @@ struct dp_option default_basic_opts[] = {
     { "ldap_access_filter", DP_OPT_STRING, NULL_STRING, NULL_STRING },
     { "ldap_netgroup_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING },
     { "ldap_group_nesting_level", DP_OPT_NUMBER, { .number = 2 }, NULL_NUMBER },
-    { "ldap_deref", DP_OPT_STRING, NULL_STRING, NULL_STRING }
+    { "ldap_deref", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ldap_account_expire_policy", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "ldap_access_order", DP_OPT_STRING, { "filter" }, NULL_STRING }
 };
 
 struct sdap_attr_map generic_attr_map[] = {
diff --git a/src/providers/ldap/ldap_init.c b/src/providers/ldap/ldap_init.c
index b6176a9..555a55e 100644
--- a/src/providers/ldap/ldap_init.c
+++ b/src/providers/ldap/ldap_init.c
@@ -225,6 +225,11 @@ int sssm_ldap_access_init(struct be_ctx *bectx,
     int ret;
     struct sdap_access_ctx *access_ctx;
     const char *filter;
+    const char *order;
+    char **order_list;
+    int order_list_len;
+    size_t c;
+    const char *dummy;
 
     access_ctx = talloc_zero(bectx, struct sdap_access_ctx);
     if(access_ctx == NULL) {
@@ -238,32 +243,92 @@ int sssm_ldap_access_init(struct be_ctx *bectx,
         goto done;
     }
 
-    filter = dp_opt_get_cstring(access_ctx->id_ctx->opts->basic,
-                                            SDAP_ACCESS_FILTER);
-    if (filter == NULL) {
-        /* It's okay if this is NULL. In that case we will simply act
-         * like the 'deny' provider.
-         */
-        DEBUG(0, ("Warning: access_provider=ldap set, "
-                  "but no ldap_access_filter configured. "
-                  "All domain users will be denied access.\n"));
+    order = dp_opt_get_cstring(access_ctx->id_ctx->opts->basic,
+                               SDAP_ACCESS_ORDER);
+    if (order == NULL) {
+        DEBUG(1, ("ldap_access_order not given, using 'filter'.\n"));
+        order = "filter";
     }
-    else {
-        if (filter[0] == '(') {
-            /* This filter is wrapped in parentheses.
-             * Pass it as-is to the openldap libraries.
-             */
-            access_ctx->filter = filter;
-        }
-        else {
-            /* Add parentheses around the filter */
-            access_ctx->filter = talloc_asprintf(access_ctx, "(%s)", filter);
-            if (access_ctx->filter == NULL) {
-                ret = ENOMEM;
-                goto done;
+
+    ret = split_on_separator(access_ctx, order, ',', true, &order_list,
+                             &order_list_len);
+    if (ret != EOK) {
+        DEBUG(1, ("split_on_separator failed.\n"));
+        goto done;
+    }
+
+    ret = check_list_for_duplicates(order_list, order_list_len, false);
+    if (ret != EOK) {
+        DEBUG(1, ("check_list_for_dublicates failed.\n"));
+        goto done;
+    }
+
+    if (order_list_len -1 > LDAP_ACCESS_LAST) {
+        DEBUG(1, ("Currently only [%d] different access rules are supported.\n"));
+        ret = EINVAL;
+        goto done;
+    }
+
+    for (c = 0; order_list[c] != NULL; c++) {
+        if (strcasecmp(order_list[c], LDAP_ACCESS_FILTER_NAME) == 0) {
+            access_ctx->access_rule[c] = LDAP_ACCESS_FILTER;
+
+            filter = dp_opt_get_cstring(access_ctx->id_ctx->opts->basic,
+                                                    SDAP_ACCESS_FILTER);
+            if (filter == NULL) {
+                /* It's okay if this is NULL. In that case we will simply act
+                 * like the 'deny' provider.
+                 */
+                DEBUG(0, ("Warning: LDAP access rule 'filter' is set, "
+                          "but no ldap_access_filter configured. "
+                          "All domain users will be denied access.\n"));
+            }
+            else {
+                if (filter[0] == '(') {
+                    /* This filter is wrapped in parentheses.
+                     * Pass it as-is to the openldap libraries.
+                     */
+                    access_ctx->filter = filter;
+                }
+                else {
+                    /* Add parentheses around the filter */
+                    access_ctx->filter = talloc_asprintf(access_ctx, "(%s)", filter);
+                    if (access_ctx->filter == NULL) {
+                        ret = ENOMEM;
+                        goto done;
+                    }
+                }
             }
+
+        } else if (strcasecmp(order_list[c], LDAP_ACCESS_EXPIRE_NAME) == 0) {
+            access_ctx->access_rule[c] = LDAP_ACCESS_EXPIRE;
+
+            dummy = dp_opt_get_cstring(access_ctx->id_ctx->opts->basic,
+                                       SDAP_ACCOUNT_EXPIRE_POLICY);
+            if (dummy == NULL) {
+                DEBUG(0, ("Warning: LDAP access rule 'expire' is set, "
+                          "but no ldap_account_expire_policy configured. "
+                          "All domain users will be denied access.\n"));
+            } else {
+                if (strcasecmp(dummy, LDAP_ACCOUNT_EXPIRE_SHADOW) != 0) {
+                    DEBUG(1, ("Unsupported LDAP account expire policy [%s].\n",
+                              dummy));
+                    ret = EINVAL;
+                    goto done;
+                }
+            }
+        } else {
+            DEBUG(1, ("Unexpected access rule name [%s].\n", order_list[c]));
+            ret = EINVAL;
+            goto done;
         }
     }
+    access_ctx->access_rule[c] = LDAP_ACCESS_EMPTY;
+    if (c == 0) {
+        DEBUG(0, ("Warning: access_provider=ldap set, "
+                  "but ldap_access_order is empty. "
+                  "All domain users will be denied access.\n"));
+    }
 
     *ops = &sdap_access_ops;
     *pvt_data = access_ctx;
diff --git a/src/providers/ldap/sdap.h b/src/providers/ldap/sdap.h
index 945de37..4d52d5b 100644
--- a/src/providers/ldap/sdap.h
+++ b/src/providers/ldap/sdap.h
@@ -182,6 +182,8 @@ enum sdap_basic_opt {
     SDAP_NETGROUP_SEARCH_BASE,
     SDAP_NESTING_LEVEL,
     SDAP_DEREF,
+    SDAP_ACCOUNT_EXPIRE_POLICY,
+    SDAP_ACCESS_ORDER,
 
     SDAP_OPTS_BASIC /* opts counter */
 };
diff --git a/src/providers/ldap/sdap_access.c b/src/providers/ldap/sdap_access.c
index 23c076f..4ebd727 100644
--- a/src/providers/ldap/sdap_access.c
+++ b/src/providers/ldap/sdap_access.c
@@ -57,6 +57,23 @@ static struct tevent_req *sdap_access_send(TALLOC_CTX *mem_ctx,
                                            struct be_ctx *be_ctx,
                                            struct sdap_access_ctx *access_ctx,
                                            const char *username);
+
+static struct tevent_req *sdap_access_filter_send(TALLOC_CTX *mem_ctx,
+                                             struct tevent_context *ev,
+                                             struct be_ctx *be_ctx,
+                                             struct sdap_access_ctx *access_ctx,
+                                             const char *username,
+                                             struct ldb_message *user_entry);
+static void sdap_access_filter_done(struct tevent_req *subreq);
+
+static struct tevent_req *sdap_account_expired_send(TALLOC_CTX *mem_ctx,
+                                             struct tevent_context *ev,
+                                             struct be_ctx *be_ctx,
+                                             struct sdap_access_ctx *access_ctx,
+                                             const char *username,
+                                             struct ldb_message *user_entry);
+static void sdap_account_expired_done(struct tevent_req *subreq);
+
 static void sdap_access_done(struct tevent_req *req);
 void sdap_pam_access_handler(struct be_req *breq)
 {
@@ -86,23 +103,15 @@ void sdap_pam_access_handler(struct be_req *breq)
 
 struct sdap_access_req_ctx {
     const char *username;
-    const char *filter;
     struct tevent_context *ev;
     struct sdap_access_ctx *access_ctx;
-    struct sdap_id_ctx *sdap_ctx;
-    struct sdap_id_op *sdap_op;
-    struct sysdb_handle *handle;
     struct be_ctx *be_ctx;
-    const char **attrs;
     int pam_status;
-    bool cached_access;
-    char *basedn;
+    struct ldb_message *user_entry;
+    size_t current_rule;
 };
 
-static void sdap_access_decide_offline(struct tevent_req *req);
-static int sdap_access_retry(struct tevent_req *req);
-static void sdap_access_connect_done(struct tevent_req *subreq);
-static void sdap_access_get_access_done(struct tevent_req *req);
+static errno_t select_next_rule(struct tevent_req *req);
 static struct tevent_req *sdap_access_send(TALLOC_CTX *mem_ctx,
                                            struct tevent_context *ev,
                                            struct be_ctx *be_ctx,
@@ -113,82 +122,352 @@ static struct tevent_req *sdap_access_send(TALLOC_CTX *mem_ctx,
     struct sdap_access_req_ctx *state;
     struct tevent_req *req;
     struct ldb_result *res;
-    const char *basedn;
-    char *clean_username;
+    const char *attrs[] = { "*", NULL };
 
     req = tevent_req_create(mem_ctx, &state, struct sdap_access_req_ctx);
     if (req == NULL) {
+        DEBUG(1, ("tevent_req_create failed.\n"));
         return NULL;
     }
 
-    if (access_ctx->filter == NULL || *access_ctx->filter == '\0') {
-        /* If no filter is set, default to restrictive */
-        DEBUG(6, ("No filter set. Access is denied.\n"));
-        state->pam_status = PAM_PERM_DENIED;
-        tevent_req_done(req);
-        tevent_req_post(req, be_ctx->ev);
-        return req;
-    }
-
-    state->filter = NULL;
     state->be_ctx = be_ctx;
     state->username = username;
     state->pam_status = PAM_SYSTEM_ERR;
-    state->sdap_ctx = access_ctx->id_ctx;
     state->ev = ev;
     state->access_ctx = access_ctx;
-
-    state->attrs = talloc_array(state, const char *, 3);
-    if (state->attrs == NULL) {
-        DEBUG(1, ("Could not allocate attributes\n"));
-        goto failed;
-    }
-
-    state->attrs[0] = SYSDB_ORIG_DN;
-    state->attrs[1] = SYSDB_LDAP_ACCESS;
-    state->attrs[2] = NULL;
+    state->current_rule = 0;
 
     DEBUG(6, ("Performing access check for user [%s]\n", username));
 
+    if (access_ctx->access_rule[0] == LDAP_ACCESS_EMPTY) {
+        DEBUG(3, ("No access rules defined, access denied.\n"));
+        state->pam_status = PAM_PERM_DENIED;
+        ret = EOK;
+        goto done;
+    }
+
     /* Get original user DN */
-    ret = sysdb_get_user_attr(state, be_ctx->sysdb,
-                              be_ctx->domain, username,
-                              state->attrs,
+    ret = sysdb_get_user_attr(state, be_ctx->sysdb, be_ctx->domain,
+                              username, attrs,
                               &res);
     if (ret != EOK) {
         if (ret == ENOENT) {
             /* If we can't find the user, return permission denied */
             state->pam_status = PAM_PERM_DENIED;
-            goto finished;
+            ret = EOK;
+            goto done;
         }
-        goto failed;
+        goto done;
     }
     else {
         if (res->count == 0) {
             /* If we can't find the user, return permission denied */
             state->pam_status = PAM_PERM_DENIED;
-            goto finished;
+            ret = EOK;
+            goto done;
         }
 
         if (res->count != 1) {
             DEBUG(1, ("Invalid response from sysdb_get_user_attr\n"));
-            goto failed;
+            ret = EINVAL;
+            goto done;
+        }
+    }
+
+    state->user_entry = res->msgs[0];
+
+    ret = select_next_rule(req);
+    if (ret != EOK) {
+        if (ret == EACCES) {
+            state->pam_status = PAM_PERM_DENIED;
+            ret = EOK;
+            goto done;
+        }
+        DEBUG(1, ("select_next_rule failed.\n"));
+        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 errno_t select_next_rule(struct tevent_req *req)
+{
+    struct sdap_access_req_ctx *state =
+            tevent_req_data(req, struct sdap_access_req_ctx);
+    struct tevent_req *subreq;
+
+    switch (state->access_ctx->access_rule[state->current_rule]) {
+        case LDAP_ACCESS_EMPTY:
+            return ENOENT;
+            break;
+        case LDAP_ACCESS_FILTER:
+            subreq = sdap_access_filter_send(state, state->ev, state->be_ctx,
+                                             state->access_ctx, state->username,
+                                             state->user_entry);
+            if (subreq == NULL) {
+                DEBUG(1, ("sdap_access_filter_send failed.\n"));
+                return ENOMEM;
+            }
+
+            tevent_req_set_callback(subreq, sdap_access_filter_done, req);
+            return EOK;
+        case LDAP_ACCESS_EXPIRE:
+            subreq = sdap_account_expired_send(state, state->ev, state->be_ctx,
+                                               state->access_ctx,
+                                               state->username,
+                                               state->user_entry);
+            if (subreq == NULL) {
+                DEBUG(1, ("sdap_account_expired_send failed.\n"));
+                return ENOMEM;
+            }
+
+            tevent_req_set_callback(subreq, sdap_account_expired_done, req);
+            return EOK;
+        default:
+            DEBUG(1, ("Unexpected access rule type. Access denied.\n"));
+    }
+
+    return EACCES;
+}
+
+static void next_access_rule(struct tevent_req *req)
+{
+    struct sdap_access_req_ctx *state =
+            tevent_req_data(req, struct sdap_access_req_ctx);
+    int ret;
+
+    if (state->pam_status == PAM_PERM_DENIED ||
+        state->pam_status == PAM_ACCT_EXPIRED) {
+        tevent_req_done(req);
+        return;
+    }
+
+    state->current_rule++;
+
+    ret = select_next_rule(req);
+    if (ret != EOK) {
+        if (ret == ENOENT) {
+            state->pam_status = PAM_SUCCESS;
+            tevent_req_done(req);
+            return;
+        } else if (ret == EACCES) {
+            state->pam_status = PAM_PERM_DENIED;
+            tevent_req_done(req);
+        } else {
+            tevent_req_error(req, ret);
+        }
+    }
+
+    return;
+}
+
+
+static errno_t sdap_account_expired_shadow(const char *username,
+                                           struct ldb_message *user_entry,
+                                           int *pam_status)
+{
+    int ret;
+    const char *val;
+    long sp_expire;
+    long today;
+
+    DEBUG(6, ("Performing access shadow check for user [%s]\n", username));
+
+    val = ldb_msg_find_attr_as_string(user_entry, SYSDB_SHADOWPW_EXPIRE, NULL);
+    if (val == NULL) {
+        DEBUG(3, ("Shadow expire attribute not found. "
+                  "Access will be granted.\n"));
+        *pam_status = PAM_SUCCESS;
+        return EOK;
+    }
+    ret = string_to_shadowpw_days(val, &sp_expire);
+    if (ret != EOK) {
+        DEBUG(1, ("Failed to retrieve shadow expire date.\n"));
+        return ret;
+    }
+
+    today = (long) (time(NULL) / (60 * 60 * 24));
+    if (sp_expire > 0 && today > sp_expire) {
+        *pam_status = PAM_ACCT_EXPIRED;
+    } else {
+        *pam_status = PAM_SUCCESS;
+    }
+
+    return EOK;
+}
+
+struct sdap_account_expired_req_ctx {
+    int pam_status;
+};
+
+static struct tevent_req *sdap_account_expired_send(TALLOC_CTX *mem_ctx,
+                                             struct tevent_context *ev,
+                                             struct be_ctx *be_ctx,
+                                             struct sdap_access_ctx *access_ctx,
+                                             const char *username,
+                                             struct ldb_message *user_entry)
+{
+    struct tevent_req *req;
+    struct sdap_account_expired_req_ctx *state;
+    int ret;
+    const char *expire;
+
+    req = tevent_req_create(mem_ctx, &state, struct sdap_account_expired_req_ctx);
+    if (req == NULL) {
+        DEBUG(1, ("tevent_req_create failed.\n"));
+        return NULL;
+    }
+
+    state->pam_status = PAM_SYSTEM_ERR;
+
+    expire = dp_opt_get_cstring(access_ctx->id_ctx->opts->basic,
+                                SDAP_ACCOUNT_EXPIRE_POLICY);
+    if (expire == NULL) {
+        DEBUG(1, ("Missing account expire policy. Access denied\n"));
+        state->pam_status = PAM_PERM_DENIED;
+        ret = EOK;
+        goto done;
+    } else {
+        if (strcasecmp(expire, LDAP_ACCOUNT_EXPIRE_SHADOW) == 0) {
+            ret = sdap_account_expired_shadow(username, user_entry,
+                                              &state->pam_status);
+            if (ret != EOK) {
+                DEBUG(1, ("sdap_account_expired_shadow failed.\n"));
+                goto done;
+            }
+        } else {
+            DEBUG(1, ("Unsupported LDAP account expire policy [%s]. "
+                      "Access denied.\n", expire));
+            state->pam_status = PAM_PERM_DENIED;
+            ret = EOK;
+            goto done;
         }
     }
 
-    /* Exactly one result returned */
-    state->cached_access = ldb_msg_find_attr_as_bool(res->msgs[0],
-                                                     SYSDB_LDAP_ACCESS,
+    ret = EOK;
+
+done:
+    if (ret == EOK) {
+        tevent_req_done(req);
+    } else {
+        tevent_req_error(req, ret);
+    }
+    tevent_req_post(req, ev);
+
+    return req;
+}
+
+static errno_t sdap_account_expired_recv(struct tevent_req *req, int *pam_status)
+{
+    struct sdap_account_expired_req_ctx *state =
+            tevent_req_data(req, struct sdap_account_expired_req_ctx);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    *pam_status = state->pam_status;
+
+    return EOK;
+}
+
+static void sdap_account_expired_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req);
+    struct sdap_access_req_ctx *state =
+            tevent_req_data(req, struct sdap_access_req_ctx);
+
+    ret = sdap_account_expired_recv(subreq, &state->pam_status);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(1, ("Error retrieving access check result.\n"));
+        state->pam_status = PAM_SYSTEM_ERR;
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    next_access_rule(req);
+
+    return;
+}
+
+
+
+struct sdap_access_filter_req_ctx {
+    const char *username;
+    const char *filter;
+    struct tevent_context *ev;
+    struct sdap_access_ctx *access_ctx;
+    struct sdap_id_ctx *sdap_ctx;
+    struct sdap_id_op *sdap_op;
+    struct sysdb_handle *handle;
+    struct be_ctx *be_ctx;
+    int pam_status;
+    bool cached_access;
+    char *basedn;
+};
+
+static void sdap_access_filter_decide_offline(struct tevent_req *req);
+static int sdap_access_filter_retry(struct tevent_req *req);
+static void sdap_access_filter_connect_done(struct tevent_req *subreq);
+static void sdap_access_filter_get_access_done(struct tevent_req *req);
+static struct tevent_req *sdap_access_filter_send(TALLOC_CTX *mem_ctx,
+                                             struct tevent_context *ev,
+                                             struct be_ctx *be_ctx,
+                                             struct sdap_access_ctx *access_ctx,
+                                             const char *username,
+                                             struct ldb_message *user_entry)
+{
+    errno_t ret;
+    struct sdap_access_filter_req_ctx *state;
+    struct tevent_req *req;
+    const char *basedn;
+    char *clean_username;
+
+    req = tevent_req_create(mem_ctx, &state, struct sdap_access_filter_req_ctx);
+    if (req == NULL) {
+        return NULL;
+    }
+
+    if (access_ctx->filter == NULL || *access_ctx->filter == '\0') {
+        /* If no filter is set, default to restrictive */
+        DEBUG(6, ("No filter set. Access is denied.\n"));
+        state->pam_status = PAM_PERM_DENIED;
+        tevent_req_done(req);
+        tevent_req_post(req, be_ctx->ev);
+        return req;
+    }
+
+    state->filter = NULL;
+    state->be_ctx = be_ctx;
+    state->username = username;
+    state->pam_status = PAM_SYSTEM_ERR;
+    state->sdap_ctx = access_ctx->id_ctx;
+    state->ev = ev;
+    state->access_ctx = access_ctx;
+
+    DEBUG(6, ("Performing access filter check for user [%s]\n", username));
+
+    state->cached_access = ldb_msg_find_attr_as_bool(user_entry,
+                                                     SYSDB_LDAP_ACCESS_FILTER,
                                                      false);
     /* Ok, we have one result, check if we are online or offline */
     if (be_is_offline(state->be_ctx)) {
         /* Ok, we're offline. Return from the cache */
-        sdap_access_decide_offline(req);
+        sdap_access_filter_decide_offline(req);
         goto finished;
     }
 
     /* Perform online operation */
-    basedn = ldb_msg_find_attr_as_string(res->msgs[0],
+    basedn = ldb_msg_find_attr_as_string(user_entry,
                                          SYSDB_ORIG_DN,
                                          NULL);
     if(basedn == NULL) {
@@ -202,7 +481,6 @@ static struct tevent_req *sdap_access_send(TALLOC_CTX *mem_ctx,
         DEBUG(1, ("Could not allocate memory for originalDN\n"));
         goto failed;
     }
-    talloc_zfree(res);
 
     /* Construct the filter */
 
@@ -232,7 +510,7 @@ static struct tevent_req *sdap_access_send(TALLOC_CTX *mem_ctx,
         goto failed;
     }
 
-    ret = sdap_access_retry(req);
+    ret = sdap_access_filter_retry(req);
     if (ret != EOK) {
         goto failed;
     }
@@ -249,10 +527,10 @@ finished:
     return req;
 }
 
-static void sdap_access_decide_offline(struct tevent_req *req)
+static void sdap_access_filter_decide_offline(struct tevent_req *req)
 {
-    struct sdap_access_req_ctx *state =
-            tevent_req_data(req, struct sdap_access_req_ctx);
+    struct sdap_access_filter_req_ctx *state =
+            tevent_req_data(req, struct sdap_access_filter_req_ctx);
 
     if (state->cached_access) {
         DEBUG(6, ("Access granted by cached credentials\n"));
@@ -263,10 +541,10 @@ static void sdap_access_decide_offline(struct tevent_req *req)
     }
 }
 
-static int sdap_access_retry(struct tevent_req *req)
+static int sdap_access_filter_retry(struct tevent_req *req)
 {
-    struct sdap_access_req_ctx *state =
-            tevent_req_data(req, struct sdap_access_req_ctx);
+    struct sdap_access_filter_req_ctx *state =
+            tevent_req_data(req, struct sdap_access_filter_req_ctx);
     struct tevent_req *subreq;
     int ret;
 
@@ -276,16 +554,16 @@ static int sdap_access_retry(struct tevent_req *req)
         return ret;
     }
 
-    tevent_req_set_callback(subreq, sdap_access_connect_done, req);
+    tevent_req_set_callback(subreq, sdap_access_filter_connect_done, req);
     return EOK;
 }
 
-static void sdap_access_connect_done(struct tevent_req *subreq)
+static void sdap_access_filter_connect_done(struct tevent_req *subreq)
 {
     struct tevent_req *req = tevent_req_callback_data(subreq,
                                                       struct tevent_req);
-    struct sdap_access_req_ctx *state =
-            tevent_req_data(req, struct sdap_access_req_ctx);
+    struct sdap_access_filter_req_ctx *state =
+            tevent_req_data(req, struct sdap_access_filter_req_ctx);
     int ret, dp_error;
 
     ret = sdap_id_op_connect_recv(subreq, &dp_error);
@@ -293,7 +571,7 @@ static void sdap_access_connect_done(struct tevent_req *subreq)
 
     if (ret != EOK) {
         if (dp_error == DP_ERR_OFFLINE) {
-            sdap_access_decide_offline(req);
+            sdap_access_filter_decide_offline(req);
             tevent_req_done(req);
             return;
         }
@@ -320,10 +598,10 @@ static void sdap_access_connect_done(struct tevent_req *subreq)
         return;
     }
 
-    tevent_req_set_callback(subreq, sdap_access_get_access_done, req);
+    tevent_req_set_callback(subreq, sdap_access_filter_get_access_done, req);
 }
 
-static void sdap_access_get_access_done(struct tevent_req *subreq)
+static void sdap_access_filter_get_access_done(struct tevent_req *subreq)
 {
     int ret, dp_error;
     size_t num_results;
@@ -332,8 +610,8 @@ static void sdap_access_get_access_done(struct tevent_req *subreq)
     struct sysdb_attrs **results;
     struct tevent_req *req =
             tevent_req_callback_data(subreq, struct tevent_req);
-    struct sdap_access_req_ctx *state =
-            tevent_req_data(req, struct sdap_access_req_ctx);
+    struct sdap_access_filter_req_ctx *state =
+            tevent_req_data(req, struct sdap_access_filter_req_ctx);
     ret = sdap_get_generic_recv(subreq, state,
                                 &num_results, &results);
     talloc_zfree(subreq);
@@ -342,13 +620,13 @@ static void sdap_access_get_access_done(struct tevent_req *subreq)
     if (ret != EOK) {
         if (dp_error == DP_ERR_OK) {
             /* retry */
-            ret = sdap_access_retry(req);
+            ret = sdap_access_filter_retry(req);
             if (ret == EOK) {
                 return;
             }
             state->pam_status = PAM_SYSTEM_ERR;
         } else if (dp_error == DP_ERR_OFFLINE) {
-            sdap_access_decide_offline(req);
+            sdap_access_filter_decide_offline(req);
         } else {
             DEBUG(1, ("sdap_get_generic_send() returned error [%d][%s]\n",
                       ret, strerror(ret)));
@@ -409,7 +687,7 @@ static void sdap_access_get_access_done(struct tevent_req *subreq)
         goto done;
     }
 
-    ret = sysdb_attrs_add_bool(attrs, SYSDB_LDAP_ACCESS,
+    ret = sysdb_attrs_add_bool(attrs, SYSDB_LDAP_ACCESS_FILTER,
                                state->pam_status == PAM_SUCCESS ?
                                                     true :
                                                     false);
@@ -445,6 +723,39 @@ done:
     }
 }
 
+static errno_t sdap_access_filter_recv(struct tevent_req *req, int *pam_status)
+{
+    struct sdap_access_filter_req_ctx *state =
+            tevent_req_data(req, struct sdap_access_filter_req_ctx);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    *pam_status = state->pam_status;
+
+    return EOK;
+}
+
+static void sdap_access_filter_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req);
+    struct sdap_access_req_ctx *state =
+            tevent_req_data(req, struct sdap_access_req_ctx);
+
+    ret = sdap_access_filter_recv(subreq, &state->pam_status);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(1, ("Error retrieving access check result.\n"));
+        state->pam_status = PAM_SYSTEM_ERR;
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    next_access_rule(req);
+
+    return;
+}
+
 static errno_t sdap_access_recv(struct tevent_req *req, int *pam_status)
 {
     struct sdap_access_req_ctx *state =
diff --git a/src/providers/ldap/sdap_access.h b/src/providers/ldap/sdap_access.h
index 5dbe864..9b8e45b 100644
--- a/src/providers/ldap/sdap_access.h
+++ b/src/providers/ldap/sdap_access.h
@@ -27,11 +27,24 @@
 
 #include "providers/dp_backend.h"
 
-#define SYSDB_LDAP_ACCESS "ldap_access_allow"
+#define SYSDB_LDAP_ACCESS_FILTER "ldap_access_filter_allow"
+
+#define LDAP_ACCESS_FILTER_NAME "filter"
+#define LDAP_ACCESS_EXPIRE_NAME "expire"
+
+#define LDAP_ACCOUNT_EXPIRE_SHADOW "shadow"
+
+enum ldap_access_rule {
+    LDAP_ACCESS_EMPTY = -1,
+    LDAP_ACCESS_FILTER = 0,
+    LDAP_ACCESS_EXPIRE,
+    LDAP_ACCESS_LAST
+};
 
 struct sdap_access_ctx {
     struct sdap_id_ctx *id_ctx;
     const char *filter;
+    int access_rule[LDAP_ACCESS_LAST + 1];
 };
 
 void ldap_pam_access_handler(struct be_req *breq);
diff --git a/src/util/util.c b/src/util/util.c
index 772a8b7..951cc37 100644
--- a/src/util/util.c
+++ b/src/util/util.c
@@ -24,6 +24,30 @@
 #include "util/util.h"
 #include "dhash.h"
 
+/* Please use this only for short lists */
+errno_t check_list_for_duplicates(char **list, size_t len, bool case_sensitive)
+{
+    size_t c;
+    size_t d;
+    int cmp;
+
+    for (c = 0; list[c] != NULL; c++) {
+        for (d = c + 1; list[d] != NULL; d++) {
+            if (case_sensitive) {
+                cmp = strcmp(list[c], list[d]);
+            } else {
+                cmp = strcasecmp(list[c], list[d]);
+            }
+            if (cmp == 0) {
+                DEBUG(1, ("Duplicate string [%s] found.\n", list[c]));
+                return EINVAL;
+            }
+        }
+    }
+
+    return EOK;
+}
+
 /* split a string into an allocated array of strings.
  * the separator is a string, and is case-sensitive.
  * optionally single values can be trimmed of of spaces and tabs */
diff --git a/src/util/util.h b/src/util/util.h
index 12d3ff0..ff10689 100644
--- a/src/util/util.h
+++ b/src/util/util.h
@@ -348,6 +348,8 @@ errno_t check_and_open_readonly(const char *filename, int *fd, const uid_t uid,
                                enum check_file_type type);
 
 /* from util.c */
+errno_t check_list_for_duplicates(char **list, size_t len, bool case_sensitive);
+
 int split_on_separator(TALLOC_CTX *mem_ctx, const char *str,
                        const char sep, bool trim, char ***_list, int *size);
 
-- 
1.7.3.2



More information about the sssd-devel mailing list