[SSSD] [PATCHES] Wildcard lookups for InfoPipe

Jakub Hrozek jhrozek at redhat.com
Thu Jun 18 08:09:06 UTC 2015


Hi,

the attached patches implement
https://fedorahosted.org/sssd/ticket/2553.

I'm sorry the patches took so long, but only during testing I realized the
deleting of users not updated by the wildcard lookup is flawed. In case
there were more entries in cache that match the wildcard than the limit
on the wildcard search, we might be deleting legitimate entries that might
contain cached credentials..

What I did instead was never remove any entries during wildcard lookups,
but instead, only return entries that were updated during the wildcard
lookup in the cache_req. That way, stale entries would be deleted by a
direct lookup (maybe as a result of getpwuid when examining a file) or
can be deleted by the cleanup task.

I'm a bit nervous about the LDAP changes, please review ruthlessly :-)
I would also like for the option defaults to be carefully checked during
review.

One question about the by-name-and-domain lookups -- should we check the
domain we receive from the cache_req is the same as we requested?

btw I really like how the cache_req and the infopipe code are structured. It
was quite easy to extend without any hacks.
-------------- next part --------------
>From 96473849475d9623588510a213a337714ec89daf Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek at redhat.com>
Date: Wed, 25 Mar 2015 10:03:43 +0100
Subject: [PATCH 01/11] tests: Move N_ELEMENTS definition to tests/common.h

Avoids code duplication
---
 src/tests/cmocka/test_nested_groups.c | 3 ---
 src/tests/cmocka/test_nss_srv.c       | 3 ---
 src/tests/common.h                    | 2 ++
 src/tests/sbus_codegen_tests.c        | 3 ---
 4 files changed, 2 insertions(+), 9 deletions(-)

diff --git a/src/tests/cmocka/test_nested_groups.c b/src/tests/cmocka/test_nested_groups.c
index eef9df2dc25c231cb81243f48b759e2fced1a85d..8081ff26102e53b2e453838c3a18e4560ac5317e 100644
--- a/src/tests/cmocka/test_nested_groups.c
+++ b/src/tests/cmocka/test_nested_groups.c
@@ -48,9 +48,6 @@
 #define GROUP_BASE_DN "cn=groups," OBJECT_BASE_DN
 #define USER_BASE_DN "cn=users," OBJECT_BASE_DN
 
-#define N_ELEMENTS(arr) \
-    (sizeof(arr) / sizeof(arr[0]))
-
 struct nested_groups_test_ctx {
     struct sss_test_ctx *tctx;
 
diff --git a/src/tests/cmocka/test_nss_srv.c b/src/tests/cmocka/test_nss_srv.c
index d1a4c16851427a36b123f04cecee5fe5ae2d333d..3ab8d39c44a8bb8cacae20f534dcbeb6ca7dec08 100644
--- a/src/tests/cmocka/test_nss_srv.c
+++ b/src/tests/cmocka/test_nss_srv.c
@@ -41,9 +41,6 @@
 #define TEST_ID_PROVIDER "ldap"
 #define TEST_DOM_SID "S-1-5-21-444379608-1639770488-2995963434"
 
-#define N_ELEMENTS(arr) \
-    (sizeof(arr) / sizeof(arr[0]))
-
 struct nss_test_ctx {
     struct sss_test_ctx *tctx;
     struct sss_domain_info *subdom;
diff --git a/src/tests/common.h b/src/tests/common.h
index 714be6988aa032d935c09bc7db524545a4c9154e..1823e9cbad78514799ade8fa5ae8ab8d7484f8a4 100644
--- a/src/tests/common.h
+++ b/src/tests/common.h
@@ -30,6 +30,8 @@
 #include "providers/data_provider.h"
 #include "providers/ldap/sdap.h"
 
+#define N_ELEMENTS(arr) (sizeof(arr) / sizeof(arr[0]))
+
 extern TALLOC_CTX *global_talloc_context;
 
 #define check_leaks(ctx, bytes) _check_leaks((ctx), (bytes), __location__)
diff --git a/src/tests/sbus_codegen_tests.c b/src/tests/sbus_codegen_tests.c
index 9e9be52e84672eb3ee3afa4e13d5f60047150e98..4637b92b84459041806bb5950e969e988716bec8 100644
--- a/src/tests/sbus_codegen_tests.c
+++ b/src/tests/sbus_codegen_tests.c
@@ -33,9 +33,6 @@
 #include "tests/sbus_codegen_tests_generated.h"
 #include "util/util_errors.h"
 
-#define N_ELEMENTS(arr) \
-    (sizeof(arr) / sizeof(arr[0]))
-
 /* The following 2 macros were taken from check's project source files (0.9.10)
  * http://check.sourceforge.net/
  */
-- 
2.4.3

-------------- next part --------------
>From 715119779fc02a461c0410e0a09fe93dfcdb5e4f Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek at redhat.com>
Date: Tue, 24 Mar 2015 23:24:22 +0100
Subject: [PATCH 02/11] SYSDB: Add functions to look up multiple entries
 including name and custom filter

Related:
    https://fedorahosted.org/sssd/ticket/2553

Adds new sysdb function:
    - sysdb_enumpwent_filter
    - sysdb_enumpwent_filter_with_views
    - sysdb_enumgrent_filter
    - sysdb_enumgrent_filter_with_views

These are similar to enumeration functions, but optionally allow to
specify a filter to be applied on user/group names. Also an additional
custom filter can be applied.
---
 src/db/sysdb.h                      |  24 ++
 src/db/sysdb_search.c               | 241 ++++++++++++------
 src/tests/cmocka/test_sysdb_views.c | 494 ++++++++++++++++++++++++++++++++++++
 3 files changed, 680 insertions(+), 79 deletions(-)

diff --git a/src/db/sysdb.h b/src/db/sysdb.h
index f667977edbaeb7cf74e20a6a7256594dfa9ff731..d8f7345c872062e8d664e1b3548c6617e4618981 100644
--- a/src/db/sysdb.h
+++ b/src/db/sysdb.h
@@ -586,10 +586,22 @@ int sysdb_enumpwent(TALLOC_CTX *mem_ctx,
                     struct sss_domain_info *domain,
                     struct ldb_result **res);
 
+int sysdb_enumpwent_filter(TALLOC_CTX *mem_ctx,
+                           struct sss_domain_info *domain,
+                           const char *name_filter,
+                           const char *addtl_filter,
+                           struct ldb_result **res);
+
 int sysdb_enumpwent_with_views(TALLOC_CTX *mem_ctx,
                                struct sss_domain_info *domain,
                                struct ldb_result **res);
 
+int sysdb_enumpwent_filter_with_views(TALLOC_CTX *mem_ctx,
+                                      struct sss_domain_info *domain,
+                                      const char *name_filter,
+                                      const char *addtl_filter,
+                                      struct ldb_result **res);
+
 int sysdb_getgrnam(TALLOC_CTX *mem_ctx,
                    struct sss_domain_info *domain,
                    const char *name,
@@ -604,10 +616,22 @@ int sysdb_enumgrent(TALLOC_CTX *mem_ctx,
                     struct sss_domain_info *domain,
                     struct ldb_result **res);
 
+int sysdb_enumgrent_filter(TALLOC_CTX *mem_ctx,
+                           struct sss_domain_info *domain,
+                           const char *name_filter,
+                           const char *addtl_filter,
+                           struct ldb_result **res);
+
 int sysdb_enumgrent_with_views(TALLOC_CTX *mem_ctx,
                                struct sss_domain_info *domain,
                                struct ldb_result **res);
 
+int sysdb_enumgrent_filter_with_views(TALLOC_CTX *mem_ctx,
+                                      struct sss_domain_info *domain,
+                                      const char *name_filter,
+                                      const char *addtl_filter,
+                                      struct ldb_result **res);
+
 struct sysdb_netgroup_ctx {
     enum {SYSDB_NETGROUP_TRIPLE_VAL, SYSDB_NETGROUP_GROUP_VAL} type;
     union {
diff --git a/src/db/sysdb_search.c b/src/db/sysdb_search.c
index a8dcc9f8d6617be8e8fb82a1c6360c6df9726a37..233537543f0c13c32b0dfdac4eaddb70c40fd6da 100644
--- a/src/db/sysdb_search.c
+++ b/src/db/sysdb_search.c
@@ -255,44 +255,93 @@ done:
     return ret;
 }
 
+static char *enum_filter(TALLOC_CTX *mem_ctx,
+                         const char *base_filter,
+                         const char *name_filter,
+                         const char *addtl_filter)
+{
+    char *filter;
+
+    if (name_filter == NULL && addtl_filter == NULL) {
+        filter = talloc_strdup(mem_ctx, base_filter);
+    } else {
+        filter = talloc_asprintf(mem_ctx, "(&%s", base_filter);
+
+        if (filter != NULL && name_filter != NULL) {
+            filter = talloc_asprintf_append(filter, "(%s=%s)",
+                                            SYSDB_NAME, name_filter);
+        }
+
+        if (filter != NULL && addtl_filter != NULL) {
+            filter = talloc_asprintf_append(filter, "%s", addtl_filter);
+        }
+
+        if (filter != NULL) {
+            filter = talloc_asprintf_append(filter, ")");
+        }
+    }
+
+    return filter;
+}
+
+int sysdb_enumpwent_filter(TALLOC_CTX *mem_ctx,
+                           struct sss_domain_info *domain,
+                           const char *name_filter,
+                           const char *addtl_filter,
+                           struct ldb_result **_res)
+{
+    TALLOC_CTX *tmp_ctx;
+    static const char *attrs[] = SYSDB_PW_ATTRS;
+    char *filter = NULL;
+    struct ldb_dn *base_dn;
+    struct ldb_result *res;
+    int ret;
+
+    tmp_ctx = talloc_new(NULL);
+    if (!tmp_ctx) {
+        return ENOMEM;
+    }
+
+    base_dn = sysdb_user_base_dn(tmp_ctx, domain);
+    if (!base_dn) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    filter = enum_filter(tmp_ctx, SYSDB_PWENT_FILTER,
+                         name_filter, addtl_filter);
+    if (filter == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+    DEBUG(SSSDBG_TRACE_LIBS, "Searching cache with [%s]\n", filter);
+
+    ret = ldb_search(domain->sysdb->ldb, tmp_ctx, &res, base_dn,
+                     LDB_SCOPE_SUBTREE, attrs, "%s", filter);
+    if (ret) {
+        ret = sysdb_error_to_errno(ret);
+        goto done;
+    }
+
+    *_res = talloc_steal(mem_ctx, res);
+
+done:
+    talloc_zfree(tmp_ctx);
+    return ret;
+}
+
 int sysdb_enumpwent(TALLOC_CTX *mem_ctx,
                     struct sss_domain_info *domain,
                     struct ldb_result **_res)
 {
-    TALLOC_CTX *tmp_ctx;
-    static const char *attrs[] = SYSDB_PW_ATTRS;
-    struct ldb_dn *base_dn;
-    struct ldb_result *res;
-    int ret;
-
-    tmp_ctx = talloc_new(NULL);
-    if (!tmp_ctx) {
-        return ENOMEM;
-    }
-
-    base_dn = sysdb_user_base_dn(tmp_ctx, domain);
-    if (!base_dn) {
-        ret = ENOMEM;
-        goto done;
-    }
-
-    ret = ldb_search(domain->sysdb->ldb, tmp_ctx, &res, base_dn,
-                     LDB_SCOPE_SUBTREE, attrs, SYSDB_PWENT_FILTER);
-    if (ret) {
-        ret = sysdb_error_to_errno(ret);
-        goto done;
-    }
-
-    *_res = talloc_steal(mem_ctx, res);
-
-done:
-    talloc_zfree(tmp_ctx);
-    return ret;
+    return sysdb_enumpwent_filter(mem_ctx, domain, NULL, 0, _res);
 }
 
-int sysdb_enumpwent_with_views(TALLOC_CTX *mem_ctx,
-                               struct sss_domain_info *domain,
-                               struct ldb_result **_res)
+int sysdb_enumpwent_filter_with_views(TALLOC_CTX *mem_ctx,
+                                      struct sss_domain_info *domain,
+                                      const char *name_filter,
+                                      const char *addtl_filter,
+                                      struct ldb_result **_res)
 {
     TALLOC_CTX *tmp_ctx;
     struct ldb_result *res;
@@ -305,7 +354,7 @@ int sysdb_enumpwent_with_views(TALLOC_CTX *mem_ctx,
         return ENOMEM;
     }
 
-    ret = sysdb_enumpwent(tmp_ctx, domain, &res);
+    ret = sysdb_enumpwent_filter(tmp_ctx, domain, name_filter, addtl_filter, &res);
     if (ret != EOK) {
         DEBUG(SSSDBG_OP_FAILURE, "sysdb_enumpwent failed.\n");
         goto done;
@@ -331,6 +380,13 @@ done:
     return ret;
 }
 
+int sysdb_enumpwent_with_views(TALLOC_CTX *mem_ctx,
+                               struct sss_domain_info *domain,
+                               struct ldb_result **_res)
+{
+    return sysdb_enumpwent_filter_with_views(mem_ctx, domain, NULL, NULL, _res);
+}
+
 /* groups */
 
 static int mpg_convert(struct ldb_message *msg)
@@ -662,57 +718,77 @@ done:
     return ret;
 }
 
+int sysdb_enumgrent_filter(TALLOC_CTX *mem_ctx,
+                           struct sss_domain_info *domain,
+                           const char *name_filter,
+                           const char *addtl_filter,
+                           struct ldb_result **_res)
+{
+    TALLOC_CTX *tmp_ctx;
+    static const char *attrs[] = SYSDB_GRSRC_ATTRS;
+    const char *filter = NULL;
+    const char *base_filter;
+    struct ldb_dn *base_dn;
+    struct ldb_result *res;
+    int ret;
+
+    tmp_ctx = talloc_new(NULL);
+    if (!tmp_ctx) {
+        return ENOMEM;
+    }
+
+    if (domain->mpg) {
+        base_filter = SYSDB_GRENT_MPG_FILTER;
+        base_dn = ldb_dn_new_fmt(tmp_ctx, domain->sysdb->ldb,
+                                 SYSDB_DOM_BASE, domain->name);
+    } else {
+        base_filter = SYSDB_GRENT_FILTER;
+        base_dn = sysdb_group_base_dn(tmp_ctx, domain);
+    }
+    if (!base_dn) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    filter = enum_filter(tmp_ctx, base_filter,
+                         name_filter, addtl_filter);
+    if (filter == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+    DEBUG(SSSDBG_TRACE_LIBS, "Searching cache with [%s]\n", filter);
+
+    ret = ldb_search(domain->sysdb->ldb, tmp_ctx, &res, base_dn,
+                     LDB_SCOPE_SUBTREE, attrs, "%s", filter);
+    if (ret) {
+        ret = sysdb_error_to_errno(ret);
+        goto done;
+    }
+
+    ret = mpg_res_convert(res);
+    if (ret) {
+        goto done;
+    }
+
+    *_res = talloc_steal(mem_ctx, res);
+
+done:
+    talloc_zfree(tmp_ctx);
+    return ret;
+}
+
 int sysdb_enumgrent(TALLOC_CTX *mem_ctx,
                     struct sss_domain_info *domain,
                     struct ldb_result **_res)
 {
-    TALLOC_CTX *tmp_ctx;
-    static const char *attrs[] = SYSDB_GRSRC_ATTRS;
-    const char *fmt_filter;
-    struct ldb_dn *base_dn;
-    struct ldb_result *res;
-    int ret;
-
-    tmp_ctx = talloc_new(NULL);
-    if (!tmp_ctx) {
-        return ENOMEM;
-    }
-
-    if (domain->mpg) {
-        fmt_filter = SYSDB_GRENT_MPG_FILTER;
-        base_dn = ldb_dn_new_fmt(tmp_ctx, domain->sysdb->ldb,
-                                 SYSDB_DOM_BASE, domain->name);
-    } else {
-        fmt_filter = SYSDB_GRENT_FILTER;
-        base_dn = sysdb_group_base_dn(tmp_ctx, domain);
-    }
-    if (!base_dn) {
-        ret = ENOMEM;
-        goto done;
-    }
-
-    ret = ldb_search(domain->sysdb->ldb, tmp_ctx, &res, base_dn,
-                     LDB_SCOPE_SUBTREE, attrs, "%s", fmt_filter);
-    if (ret) {
-        ret = sysdb_error_to_errno(ret);
-        goto done;
-    }
-
-    ret = mpg_res_convert(res);
-    if (ret) {
-        goto done;
-    }
-
-    *_res = talloc_steal(mem_ctx, res);
-
-done:
-    talloc_zfree(tmp_ctx);
-    return ret;
+    return sysdb_enumgrent_filter(mem_ctx, domain, NULL, 0, _res);
 }
 
-int sysdb_enumgrent_with_views(TALLOC_CTX *mem_ctx,
-                               struct sss_domain_info *domain,
-                               struct ldb_result **_res)
+int sysdb_enumgrent_filter_with_views(TALLOC_CTX *mem_ctx,
+                                      struct sss_domain_info *domain,
+                                      const char *name_filter,
+                                      const char *addtl_filter,
+                                      struct ldb_result **_res)
 {
     TALLOC_CTX *tmp_ctx;
     struct ldb_result *res;
@@ -725,7 +801,7 @@ int sysdb_enumgrent_with_views(TALLOC_CTX *mem_ctx,
         return ENOMEM;
     }
 
-    ret = sysdb_enumgrent(tmp_ctx, domain,&res);
+    ret = sysdb_enumgrent_filter(tmp_ctx, domain, name_filter, addtl_filter, &res);
     if (ret != EOK) {
         DEBUG(SSSDBG_OP_FAILURE, "sysdb_enumgrent failed.\n");
         goto done;
@@ -759,6 +835,13 @@ done:
     return ret;
 }
 
+int sysdb_enumgrent_with_views(TALLOC_CTX *mem_ctx,
+                               struct sss_domain_info *domain,
+                               struct ldb_result **_res)
+{
+    return sysdb_enumgrent_filter_with_views(mem_ctx, domain, NULL, NULL, _res);
+}
+
 int sysdb_initgroups(TALLOC_CTX *mem_ctx,
                      struct sss_domain_info *domain,
                      const char *name,
diff --git a/src/tests/cmocka/test_sysdb_views.c b/src/tests/cmocka/test_sysdb_views.c
index 69118cd87a172696f8220e1446df7a856e368cb6..1fb598219e9ee581e465ddbb32ba9f2544600c26 100644
--- a/src/tests/cmocka/test_sysdb_views.c
+++ b/src/tests/cmocka/test_sysdb_views.c
@@ -45,6 +45,7 @@
 #define TEST_USER_HOMEDIR "/home/home"
 #define TEST_USER_SHELL "/bin/shell"
 #define TEST_USER_SID "S-1-2-3-4"
+#define TEST_GID_OVERRIDE_BASE 100
 
 struct sysdb_test_ctx {
     struct sysdb_ctx *sysdb;
@@ -108,6 +109,7 @@ static int _setup_sysdb_tests(struct sysdb_test_ctx **ctx, bool enumerate)
                            TESTS_PATH, &test_ctx->domain);
     assert_int_equal(ret, EOK);
 
+    test_ctx->domain->has_views = true;
     test_ctx->sysdb = test_ctx->domain->sysdb;
 
     *ctx = test_ctx;
@@ -115,6 +117,7 @@ static int _setup_sysdb_tests(struct sysdb_test_ctx **ctx, bool enumerate)
 }
 
 #define setup_sysdb_tests(ctx) _setup_sysdb_tests((ctx), false)
+#define setup_sysdb_enum_tests(ctx) _setup_sysdb_tests((ctx), true)
 
 static int test_sysdb_setup(void **state)
 {
@@ -426,6 +429,473 @@ void test_sysdb_invalidate_overrides(void **state)
     assert_int_equal(ldb_msg_find_attr_as_uint64(msg, SYSDB_CACHE_EXPIRE, 0),
                      1);
     assert_null(ldb_msg_find_attr_as_string(msg, SYSDB_OVERRIDE_DN, NULL));
+
+    ret = sysdb_delete_user(test_ctx->domain, TEST_USER_NAME, 0);
+    assert_int_equal(ret, EOK);
+}
+
+static const char *users[] = { "alice", "bob", "barney", NULL };
+
+static void enum_test_user_override(struct sysdb_test_ctx *test_ctx,
+                                    const char *name)
+{
+    int ret;
+    struct sysdb_attrs *attrs;
+    struct ldb_dn *dn;
+    TALLOC_CTX *tmp_ctx;
+    const char *anchor;
+    const char *override_gecos;
+
+    tmp_ctx = talloc_new(test_ctx);
+    assert_non_null(tmp_ctx);
+
+    attrs = sysdb_new_attrs(tmp_ctx);
+    assert_non_null(attrs);
+
+    dn = sysdb_user_dn(tmp_ctx, test_ctx->domain, name);
+    assert_non_null(dn);
+
+    anchor = talloc_asprintf(tmp_ctx, "%s%s", TEST_ANCHOR_PREFIX, name);
+    ret = sysdb_attrs_add_string(attrs, SYSDB_OVERRIDE_ANCHOR_UUID, anchor);
+    assert_int_equal(ret, EOK);
+
+    override_gecos = talloc_asprintf(attrs, "%s_GECOS_OVERRIDE", name);
+    ret = sysdb_attrs_add_string(attrs, SYSDB_GECOS, override_gecos);
+    assert_int_equal(ret, EOK);
+
+    ret = sysdb_store_override(test_ctx->domain, TEST_VIEW_NAME,
+                               SYSDB_MEMBER_USER, attrs, dn);
+    assert_int_equal(ret, EOK);
+
+    talloc_free(tmp_ctx);
+}
+
+static void enum_test_add_users(struct sysdb_test_ctx *test_ctx,
+                                const char *usernames[])
+{
+    int i;
+    int ret;
+    struct sysdb_attrs *attrs;
+
+    for (i = 0; usernames[i] != NULL; i++) {
+        attrs = talloc(test_ctx, struct sysdb_attrs);
+        assert_non_null(attrs);
+
+        ret = sysdb_store_user(test_ctx->domain, usernames[i],
+                               NULL, 0, 0, usernames[i], "/", "/bin/sh",
+                               NULL, NULL, NULL, 1, 1234 + i);
+        assert_int_equal(ret, EOK);
+
+        enum_test_user_override(test_ctx, usernames[i]);
+
+        talloc_free(attrs);
+    }
+}
+
+static void enum_test_del_users(struct sss_domain_info *dom,
+                               const char *usernames[])
+{
+    int i;
+    int ret;
+
+    for (i = 0; usernames[i] != NULL; i++) {
+        ret = sysdb_delete_user(dom, usernames[i], 0);
+        if (ret != EOK && ret != ENOENT) {
+            fail();
+        }
+    }
+}
+
+static int test_enum_users_setup(void **state)
+{
+    int ret;
+    struct sysdb_test_ctx *test_ctx;
+
+    assert_true(leak_check_setup());
+
+    ret = setup_sysdb_enum_tests(&test_ctx);
+    assert_int_equal(ret, EOK);
+
+    enum_test_add_users(test_ctx, users);
+
+    *state = (void *) test_ctx;
+    return 0;
+}
+
+static void assert_user_attrs(struct ldb_message *msg,
+                              const char *name,
+                              bool has_views)
+{
+    const char *str;
+
+    str = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL);
+    assert_string_equal(str, name);
+    str = ldb_msg_find_attr_as_string(msg, SYSDB_GECOS, NULL);
+    assert_string_equal(str, name);
+
+    str = ldb_msg_find_attr_as_string(msg, OVERRIDE_PREFIX SYSDB_GECOS, NULL);
+    if (has_views) {
+        char *override;
+
+        assert_non_null(str);
+        override = talloc_asprintf(msg, "%s_GECOS_OVERRIDE", name);
+        assert_non_null(override);
+
+        assert_string_equal(str, override);
+        talloc_free(override);
+    } else {
+        assert_null(str);
+    }
+}
+
+static int test_enum_users_teardown(void **state)
+{
+    struct sysdb_test_ctx *test_ctx = talloc_get_type_abort(*state,
+                                                        struct sysdb_test_ctx);
+
+    enum_test_del_users(test_ctx->domain, users);
+    return test_sysdb_teardown(state);
+}
+
+static void check_enumpwent(int ret, struct ldb_result *res, bool views)
+{
+    assert_int_equal(ret, EOK);
+    assert_int_equal(res->count, N_ELEMENTS(users)-1);
+    assert_user_attrs(res->msgs[0], "barney", views);
+    assert_user_attrs(res->msgs[1], "alice", views);
+    assert_user_attrs(res->msgs[2], "bob", views);
+}
+
+static void test_sysdb_enumpwent(void **state)
+{
+    int ret;
+    struct sysdb_test_ctx *test_ctx = talloc_get_type_abort(*state,
+                                                        struct sysdb_test_ctx);
+    struct ldb_result *res;
+
+    ret = sysdb_enumpwent(test_ctx, test_ctx->domain, &res);
+    check_enumpwent(ret, res, false);
+}
+
+static void test_sysdb_enumpwent_views(void **state)
+{
+    int ret;
+    struct sysdb_test_ctx *test_ctx = talloc_get_type_abort(*state,
+                                                        struct sysdb_test_ctx);
+    struct ldb_result *res;
+
+    ret = sysdb_enumpwent_with_views(test_ctx, test_ctx->domain, &res);
+    check_enumpwent(ret, res, true);
+}
+
+static void test_sysdb_enumpwent_filter(void **state)
+{
+    int ret;
+    struct sysdb_test_ctx *test_ctx = talloc_get_type_abort(*state,
+                                                        struct sysdb_test_ctx);
+    struct ldb_result *res;
+    char *addtl_filter;
+
+    ret = sysdb_enumpwent_filter(test_ctx, test_ctx->domain, "a*", 0, &res);
+    assert_int_equal(ret, EOK);
+    assert_int_equal(res->count, 1);
+    assert_user_attrs(res->msgs[0], "alice", false);
+
+    ret = sysdb_enumpwent_filter(test_ctx, test_ctx->domain, "b*", 0, &res);
+    assert_int_equal(ret, EOK);
+    assert_int_equal(res->count, 2);
+    assert_user_attrs(res->msgs[0], "barney", false);
+    assert_user_attrs(res->msgs[1], "bob", false);
+
+    ret = sysdb_enumpwent_filter(test_ctx, test_ctx->domain, "c*", 0, &res);
+    assert_int_equal(ret, EOK);
+    assert_int_equal(res->count, 0);
+
+    ret = sysdb_enumpwent_filter(test_ctx, test_ctx->domain, "*", 0, &res);
+    assert_int_equal(ret, EOK);
+    assert_int_equal(res->count, N_ELEMENTS(users)-1);
+
+    /* Test searching based on time as well */
+    addtl_filter = talloc_asprintf(test_ctx, "(%s<=%d)",
+                                   SYSDB_LAST_UPDATE, 1233);
+    ret = sysdb_enumpwent_filter(test_ctx, test_ctx->domain, "a*", addtl_filter, &res);
+    talloc_free(addtl_filter);
+    assert_int_equal(ret, EOK);
+    assert_int_equal(res->count, 0);
+
+    addtl_filter = talloc_asprintf(test_ctx, "(%s<=%d)",
+                                   SYSDB_LAST_UPDATE, 1234);
+    ret = sysdb_enumpwent_filter(test_ctx, test_ctx->domain, "a*", addtl_filter, &res);
+    talloc_free(addtl_filter);
+    assert_int_equal(ret, EOK);
+    assert_int_equal(res->count, 1);
+    assert_user_attrs(res->msgs[0], "alice", false);
+}
+
+static void test_sysdb_enumpwent_filter_views(void **state)
+{
+    int ret;
+    struct sysdb_test_ctx *test_ctx = talloc_get_type_abort(*state,
+                                                        struct sysdb_test_ctx);
+    struct ldb_result *res;
+    char *addtl_filter;
+
+    ret = sysdb_enumpwent_filter_with_views(test_ctx, test_ctx->domain,
+                                            "a*", NULL, &res);
+    assert_int_equal(ret, EOK);
+    assert_int_equal(res->count, 1);
+    assert_user_attrs(res->msgs[0], "alice", true);
+
+    ret = sysdb_enumpwent_filter_with_views(test_ctx, test_ctx->domain,
+                                            "b*", NULL, &res);
+    assert_int_equal(ret, EOK);
+    assert_int_equal(res->count, 2);
+    assert_user_attrs(res->msgs[0], "barney", true);
+    assert_user_attrs(res->msgs[1], "bob", true);
+
+    addtl_filter = talloc_asprintf(test_ctx, "(%s<=%d)",
+                                   SYSDB_LAST_UPDATE, 1235);
+    ret = sysdb_enumpwent_filter_with_views(test_ctx, test_ctx->domain,
+                                            "b*", addtl_filter, &res);
+    talloc_free(addtl_filter);
+    assert_int_equal(ret, EOK);
+    assert_int_equal(res->count, 1);
+    assert_user_attrs(res->msgs[0], "bob", true);
+
+    ret = sysdb_enumpwent_filter_with_views(test_ctx,
+                                            test_ctx->domain, "c*", NULL, &res);
+    assert_int_equal(ret, EOK);
+    assert_int_equal(res->count, 0);
+
+    ret = sysdb_enumpwent_filter_with_views(test_ctx,
+                                            test_ctx->domain, "*", NULL, &res);
+    check_enumpwent(ret, res, true);
+}
+
+static const char *groups[] = { "one", "two", "three", NULL };
+
+static void enum_test_group_override(struct sysdb_test_ctx *test_ctx,
+                                     const char *name,
+                                     unsigned override_gid)
+{
+    int ret;
+    struct sysdb_attrs *attrs;
+    struct ldb_dn *dn;
+    TALLOC_CTX *tmp_ctx;
+    const char *anchor;
+
+    tmp_ctx = talloc_new(test_ctx);
+    assert_non_null(tmp_ctx);
+
+    attrs = sysdb_new_attrs(tmp_ctx);
+    assert_non_null(attrs);
+
+    dn = sysdb_group_dn(tmp_ctx, test_ctx->domain, name);
+    assert_non_null(dn);
+
+    anchor = talloc_asprintf(tmp_ctx, "%s%s", TEST_ANCHOR_PREFIX, name);
+    ret = sysdb_attrs_add_string(attrs, SYSDB_OVERRIDE_ANCHOR_UUID, anchor);
+    assert_int_equal(ret, EOK);
+
+    ret = sysdb_attrs_add_uint32(attrs, SYSDB_GIDNUM, override_gid);
+    assert_int_equal(ret, EOK);
+
+    ret = sysdb_store_override(test_ctx->domain, TEST_VIEW_NAME,
+                               SYSDB_MEMBER_GROUP, attrs, dn);
+    assert_int_equal(ret, EOK);
+
+    talloc_free(tmp_ctx);
+}
+
+static void enum_test_add_groups(struct sysdb_test_ctx *test_ctx,
+                                 const char *groupnames[])
+{
+    int i;
+    int ret;
+    struct sysdb_attrs *attrs;
+
+    for (i = 0; groupnames[i] != NULL; i++) {
+        attrs = talloc(test_ctx, struct sysdb_attrs);
+        assert_non_null(attrs);
+
+        ret = sysdb_store_group(test_ctx->domain, groupnames[i],
+                                0, NULL, 1, 1234 + i);
+        assert_int_equal(ret, EOK);
+
+        enum_test_group_override(test_ctx, groupnames[i],
+                                 TEST_GID_OVERRIDE_BASE + i);
+        talloc_free(attrs);
+    }
+}
+
+static void enum_test_del_groups(struct sss_domain_info *dom,
+                                 const char *groupnames[])
+{
+    int i;
+    int ret;
+
+    for (i = 0; groupnames[i] != NULL; i++) {
+        ret = sysdb_delete_group(dom, groupnames[i], 0);
+        if (ret != EOK && ret != ENOENT) {
+            fail();
+        }
+    }
+}
+
+static int test_enum_groups_setup(void **state)
+{
+    int ret;
+    struct sysdb_test_ctx *test_ctx;
+
+    assert_true(leak_check_setup());
+
+    ret = setup_sysdb_enum_tests(&test_ctx);
+    assert_int_equal(ret, EOK);
+
+    enum_test_add_groups(test_ctx, groups);
+
+    *state = (void *) test_ctx;
+    return 0;
+}
+
+static int test_enum_groups_teardown(void **state)
+{
+    struct sysdb_test_ctx *test_ctx = talloc_get_type_abort(*state,
+                                                        struct sysdb_test_ctx);
+
+    enum_test_del_groups(test_ctx->domain, groups);
+    return test_sysdb_teardown(state);
+}
+
+static void assert_group_attrs(struct ldb_message *msg,
+                               const char *name,
+                               unsigned expected_override_gid)
+{
+    const char *str;
+    unsigned gid;
+
+    str = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL);
+    assert_string_equal(str, name);
+
+    if (expected_override_gid) {
+        gid = ldb_msg_find_attr_as_uint64(msg,
+                                          OVERRIDE_PREFIX SYSDB_GIDNUM, 0);
+        assert_int_equal(gid, expected_override_gid);
+    }
+}
+
+static void check_enumgrent(int ret, struct ldb_result *res, bool views)
+{
+    assert_int_equal(ret, EOK);
+    assert_int_equal(res->count, N_ELEMENTS(groups)-1);
+    assert_group_attrs(res->msgs[0], "three", views ? TEST_GID_OVERRIDE_BASE + 2 : 0);
+    assert_group_attrs(res->msgs[1], "one", views ? TEST_GID_OVERRIDE_BASE : 0);
+    assert_group_attrs(res->msgs[2], "two", views ? TEST_GID_OVERRIDE_BASE + 1 : 0);
+}
+
+static void test_sysdb_enumgrent(void **state)
+{
+    int ret;
+    struct sysdb_test_ctx *test_ctx = talloc_get_type_abort(*state,
+                                                        struct sysdb_test_ctx);
+    struct ldb_result *res;
+
+    ret = sysdb_enumgrent(test_ctx, test_ctx->domain, &res);
+    check_enumgrent(ret, res, false);
+}
+
+static void test_sysdb_enumgrent_views(void **state)
+{
+    int ret;
+    struct sysdb_test_ctx *test_ctx = talloc_get_type_abort(*state,
+                                                        struct sysdb_test_ctx);
+    struct ldb_result *res;
+
+    ret = sysdb_enumgrent_with_views(test_ctx, test_ctx->domain, &res);
+    check_enumgrent(ret, res, true);
+}
+
+static void test_sysdb_enumgrent_filter(void **state)
+{
+    int ret;
+    struct sysdb_test_ctx *test_ctx = talloc_get_type_abort(*state,
+                                                        struct sysdb_test_ctx);
+    struct ldb_result *res;
+    char *addtl_filter;
+
+    ret = sysdb_enumgrent_filter(test_ctx, test_ctx->domain, "o*", 0, &res);
+    assert_int_equal(ret, EOK);
+    assert_int_equal(res->count, 1);
+    assert_group_attrs(res->msgs[0], "one", 0);
+
+    ret = sysdb_enumgrent_filter(test_ctx, test_ctx->domain, "t*", 0, &res);
+    assert_int_equal(ret, EOK);
+    assert_int_equal(res->count, 2);
+    assert_group_attrs(res->msgs[0], "three", 0);
+    assert_group_attrs(res->msgs[1], "two", 0);
+
+    ret = sysdb_enumgrent_filter(test_ctx, test_ctx->domain, "x*", 0, &res);
+    assert_int_equal(ret, EOK);
+    assert_int_equal(res->count, 0);
+
+    ret = sysdb_enumgrent_filter(test_ctx, test_ctx->domain, "*", 0, &res);
+    check_enumgrent(ret, res, false);
+
+    addtl_filter = talloc_asprintf(test_ctx, "(%s<=%d)",
+                                   SYSDB_LAST_UPDATE, 1233);
+    ret = sysdb_enumgrent_filter(test_ctx, test_ctx->domain, "o*", addtl_filter, &res);
+    talloc_free(addtl_filter);
+    assert_int_equal(ret, EOK);
+    assert_int_equal(res->count, 0);
+
+    addtl_filter = talloc_asprintf(test_ctx, "(%s<=%d)",
+                                   SYSDB_LAST_UPDATE, 1234);
+    ret = sysdb_enumgrent_filter(test_ctx, test_ctx->domain, "o*", addtl_filter, &res);
+    talloc_free(addtl_filter);
+    assert_int_equal(ret, EOK);
+    assert_int_equal(res->count, 1);
+    assert_group_attrs(res->msgs[0], "one", 0);
+
+}
+
+static void test_sysdb_enumgrent_filter_views(void **state)
+{
+    int ret;
+    struct sysdb_test_ctx *test_ctx = talloc_get_type_abort(*state,
+                                                        struct sysdb_test_ctx);
+    struct ldb_result *res;
+    char *addtl_filter;
+
+    ret = sysdb_enumgrent_filter_with_views(test_ctx, test_ctx->domain,
+                                            "o*", NULL, &res);
+    assert_int_equal(ret, EOK);
+    assert_int_equal(res->count, 1);
+    assert_group_attrs(res->msgs[0], "one", TEST_GID_OVERRIDE_BASE);
+
+    ret = sysdb_enumgrent_filter_with_views(test_ctx, test_ctx->domain,
+                                            "t*", NULL, &res);
+    assert_int_equal(ret, EOK);
+    assert_int_equal(res->count, 2);
+    assert_group_attrs(res->msgs[0], "three", TEST_GID_OVERRIDE_BASE + 2);
+    assert_group_attrs(res->msgs[1], "two", TEST_GID_OVERRIDE_BASE + 1);
+
+    addtl_filter = talloc_asprintf(test_ctx, "(%s<=%d)",
+                                   SYSDB_LAST_UPDATE, 1235);
+    ret = sysdb_enumgrent_filter_with_views(test_ctx, test_ctx->domain,
+                                            "t*", addtl_filter, &res);
+    talloc_free(addtl_filter);
+    assert_int_equal(ret, EOK);
+    assert_int_equal(res->count, 1);
+    assert_group_attrs(res->msgs[0], "two", TEST_GID_OVERRIDE_BASE + 1);
+
+    ret = sysdb_enumgrent_filter_with_views(test_ctx, test_ctx->domain,
+                                            "x*", NULL, &res);
+    assert_int_equal(ret, EOK);
+    assert_int_equal(res->count, 0);
+
+    ret = sysdb_enumgrent_filter_with_views(test_ctx, test_ctx->domain,
+                                            "*", NULL, &res);
+    check_enumgrent(ret, res, true);
 }
 
 int main(int argc, const char *argv[])
@@ -453,6 +923,30 @@ int main(int argc, const char *argv[])
                                         test_sysdb_setup, test_sysdb_teardown),
         cmocka_unit_test_setup_teardown(test_sysdb_invalidate_overrides,
                                         test_sysdb_setup, test_sysdb_teardown),
+        cmocka_unit_test_setup_teardown(test_sysdb_enumpwent,
+                                        test_enum_users_setup,
+                                        test_enum_users_teardown),
+        cmocka_unit_test_setup_teardown(test_sysdb_enumpwent_views,
+                                        test_enum_users_setup,
+                                        test_enum_users_teardown),
+        cmocka_unit_test_setup_teardown(test_sysdb_enumpwent_filter,
+                                        test_enum_users_setup,
+                                        test_enum_users_teardown),
+        cmocka_unit_test_setup_teardown(test_sysdb_enumpwent_filter_views,
+                                        test_enum_users_setup,
+                                        test_enum_users_teardown),
+        cmocka_unit_test_setup_teardown(test_sysdb_enumgrent,
+                                        test_enum_groups_setup,
+                                        test_enum_groups_teardown),
+        cmocka_unit_test_setup_teardown(test_sysdb_enumgrent_views,
+                                        test_enum_groups_setup,
+                                        test_enum_groups_teardown),
+        cmocka_unit_test_setup_teardown(test_sysdb_enumgrent_filter,
+                                        test_enum_groups_setup,
+                                        test_enum_groups_teardown),
+        cmocka_unit_test_setup_teardown(test_sysdb_enumgrent_filter_views,
+                                        test_enum_groups_setup,
+                                        test_enum_groups_teardown),
     };
 
     /* Set debug level to invalid value so we can deside if -d 0 was used. */
-- 
2.4.3

-------------- next part --------------
>From fa77303721e254c55a36f9c991d55d9977d818b3 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek at redhat.com>
Date: Mon, 4 May 2015 12:34:32 +0200
Subject: [PATCH 03/11] DP: Add DP_WILDCARD and
 SSS_DP_WILDCARD_USER/SSS_DP_WILDCARD_GROUP

Related:
    https://fedorahosted.org/sssd/ticket/2553

Extends the Data Provider interface and the responder<->Data provider
interface with wildcard lookups.

The patch uses a new "wildcard" prefix rather than reusing the existing
user/group prefixes.
---
 src/providers/data_provider.h       |  4 ++++
 src/providers/data_provider_be.c    |  5 +++++
 src/responder/common/responder.h    |  4 +++-
 src/responder/common/responder_dp.c | 11 +++++++++++
 4 files changed, 23 insertions(+), 1 deletion(-)

diff --git a/src/providers/data_provider.h b/src/providers/data_provider.h
index 657d2b798cf28fa59c9f644e343f2a2482609bad..8a56b857d93925a4c35243f9e3b04d4645634175 100644
--- a/src/providers/data_provider.h
+++ b/src/providers/data_provider.h
@@ -128,6 +128,7 @@
 #define BE_FILTER_ENUM 3
 #define BE_FILTER_SECID 4
 #define BE_FILTER_UUID 5
+#define BE_FILTER_WILDCARD 6
 
 #define BE_REQ_USER          0x0001
 #define BE_REQ_GROUP         0x0002
@@ -149,6 +150,9 @@
  * length */
 #define DP_SEC_ID_LEN (sizeof(DP_SEC_ID) - 1)
 
+#define DP_WILDCARD "wildcard"
+#define DP_WILDCARD_LEN (sizeof(DP_WILDCARD) - 1)
+
 #define EXTRA_NAME_IS_UPN "U"
 #define EXTRA_INPUT_MAYBE_WITH_VIEW "V"
 
diff --git a/src/providers/data_provider_be.c b/src/providers/data_provider_be.c
index 34c7b2c766ee4072ee980ad43f1e1d33ee7c8a5a..dbc74446459e2fceb8aad90c12aa852eb4c8e367 100644
--- a/src/providers/data_provider_be.c
+++ b/src/providers/data_provider_be.c
@@ -1199,6 +1199,11 @@ static int be_get_account_info(struct sbus_request *dbus_req, void *user_data)
             ret = split_name_extended(req, &filter[DP_SEC_ID_LEN + 1],
                                       &req->filter_value,
                                       &req->extra_value);
+        } else if (strncmp(filter, DP_WILDCARD"=", DP_WILDCARD_LEN + 1) == 0) {
+            req->filter_type = BE_FILTER_WILDCARD;
+            ret = split_name_extended(req, &filter[DP_WILDCARD_LEN + 1],
+                                      &req->filter_value,
+                                      &req->extra_value);
         } else if (strcmp(filter, ENUM_INDICATOR) == 0) {
             req->filter_type = BE_FILTER_ENUM;
             req->filter_value = NULL;
diff --git a/src/responder/common/responder.h b/src/responder/common/responder.h
index 9c7a73809a3de8859aa546c2a7d31620fea39ac9..7bf70b013a02c4809c48560ec40a7c79639dc075 100644
--- a/src/responder/common/responder.h
+++ b/src/responder/common/responder.h
@@ -281,7 +281,9 @@ enum sss_dp_acct_type {
     SSS_DP_NETGR,
     SSS_DP_SERVICES,
     SSS_DP_SECID,
-    SSS_DP_USER_AND_GROUP
+    SSS_DP_USER_AND_GROUP,
+    SSS_DP_WILDCARD_USER,
+    SSS_DP_WILDCARD_GROUP
 };
 
 struct tevent_req *
diff --git a/src/responder/common/responder_dp.c b/src/responder/common/responder_dp.c
index 853b3eae3877a8b6b2ffb310a0fb9ccf017498ae..bc8304e767b29a13d05e96afb040e810d3139412 100644
--- a/src/responder/common/responder_dp.c
+++ b/src/responder/common/responder_dp.c
@@ -528,9 +528,11 @@ sss_dp_get_account_msg(void *pvt)
 
     switch (info->type) {
         case SSS_DP_USER:
+        case SSS_DP_WILDCARD_USER:
             be_type = BE_REQ_USER;
             break;
         case SSS_DP_GROUP:
+        case SSS_DP_WILDCARD_GROUP:
             be_type = BE_REQ_GROUP;
             break;
         case SSS_DP_INITGROUPS:
@@ -563,6 +565,15 @@ sss_dp_get_account_msg(void *pvt)
                 filter = talloc_asprintf(info, "%s=%s", DP_SEC_ID,
                                                info->opt_name);
             }
+        } else if (info->type == SSS_DP_WILDCARD_USER ||
+                   info->type == SSS_DP_WILDCARD_GROUP) {
+            if (info->extra) {
+                filter = talloc_asprintf(info, "%s=%s:%s", DP_WILDCARD,
+                                               info->opt_name, info->extra);
+            } else {
+                filter = talloc_asprintf(info, "%s=%s", DP_WILDCARD,
+                                               info->opt_name);
+            }
         } else {
             if (info->extra) {
                 filter = talloc_asprintf(info, "name=%s:%s",
-- 
2.4.3

-------------- next part --------------
>From 5d75120a654898b0ecce721950995d816af977f8 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek at redhat.com>
Date: Tue, 24 Mar 2015 23:24:50 +0100
Subject: [PATCH 04/11] cache_req: Extend cache_req with wildcard lookups

Related:
    https://fedorahosted.org/sssd/ticket/2553

Adds two new functions to the cache_req API:
    - cache_req_user_by_filter_send
    - cache_req_group_by_filter_send

These functions can be used to retrieve users or groups that match a
specified filter.

Also renames a variable to avoid constant confusion -- the variable is
only used for debug output.
---
 src/responder/common/responder_cache_req.c  | 135 ++++++++-
 src/responder/common/responder_cache_req.h  |  22 ++
 src/tests/cmocka/test_responder_cache_req.c | 414 +++++++++++++++++++++++++++-
 3 files changed, 561 insertions(+), 10 deletions(-)

diff --git a/src/responder/common/responder_cache_req.c b/src/responder/common/responder_cache_req.c
index 7ba257276b064d923b6144f45ade59cac48849d5..786c4f22bdce3e5d6c57e602b6763ce5f19e8b1d 100644
--- a/src/responder/common/responder_cache_req.c
+++ b/src/responder/common/responder_cache_req.c
@@ -50,6 +50,8 @@ struct cache_req_input {
 
     /* Fully qualified object name used in debug messages. */
     const char *debug_fqn;
+    /* Time when the request started. Useful for by-filter lookups */
+    time_t req_start;
 };
 
 struct cache_req_input *
@@ -66,11 +68,14 @@ cache_req_input_create(TALLOC_CTX *mem_ctx,
     }
 
     input->type = type;
+    input->req_start = time(NULL);
 
     /* Check that input parameters match selected type. */
     switch (input->type) {
     case CACHE_REQ_USER_BY_NAME:
     case CACHE_REQ_GROUP_BY_NAME:
+    case CACHE_REQ_USER_BY_FILTER:
+    case CACHE_REQ_GROUP_BY_FILTER:
     case CACHE_REQ_INITGROUPS:
         if (name == NULL) {
             DEBUG(SSSDBG_CRIT_FAILURE, "Bug: name cannot be NULL!\n");
@@ -108,6 +113,14 @@ cache_req_input_create(TALLOC_CTX *mem_ctx,
     case CACHE_REQ_INITGROUPS:
         input->dp_type = SSS_DP_INITGROUPS;
         break;
+
+    case CACHE_REQ_USER_BY_FILTER:
+        input->dp_type = SSS_DP_WILDCARD_USER;
+        break;
+
+    case CACHE_REQ_GROUP_BY_FILTER:
+        input->dp_type = SSS_DP_WILDCARD_GROUP;
+        break;
     }
 
     return input;
@@ -141,7 +154,7 @@ cache_req_input_set_domain(struct cache_req_input *input,
 {
     TALLOC_CTX *tmp_ctx = NULL;
     const char *name = NULL;
-    const char *fqn = NULL;
+    const char *debug_fqn = NULL;
     errno_t ret;
 
     tmp_ctx = talloc_new(NULL);
@@ -155,6 +168,8 @@ cache_req_input_set_domain(struct cache_req_input *input,
     switch (input->type) {
     case CACHE_REQ_USER_BY_NAME:
     case CACHE_REQ_GROUP_BY_NAME:
+    case CACHE_REQ_USER_BY_FILTER:
+    case CACHE_REQ_GROUP_BY_FILTER:
     case CACHE_REQ_INITGROUPS:
         name = sss_get_cased_name(tmp_ctx, input->orig_name,
                                   domain->case_sensitive);
@@ -169,8 +184,8 @@ cache_req_input_set_domain(struct cache_req_input *input,
             goto done;
         }
 
-        fqn = talloc_asprintf(tmp_ctx, "%s@%s", name, domain->name);
-        if (fqn == NULL) {
+        debug_fqn = talloc_asprintf(tmp_ctx, "%s@%s", name, domain->name);
+        if (debug_fqn == NULL) {
             ret = ENOMEM;
             goto done;
         }
@@ -178,16 +193,16 @@ cache_req_input_set_domain(struct cache_req_input *input,
         break;
 
     case CACHE_REQ_USER_BY_ID:
-        fqn = talloc_asprintf(tmp_ctx, "UID:%d@%s", input->id, domain->name);
-        if (fqn == NULL) {
+        debug_fqn = talloc_asprintf(tmp_ctx, "UID:%d@%s", input->id, domain->name);
+        if (debug_fqn == NULL) {
             ret = ENOMEM;
             goto done;
         }
         break;
 
     case CACHE_REQ_GROUP_BY_ID:
-        fqn = talloc_asprintf(tmp_ctx, "GID:%d@%s", input->id, domain->name);
-        if (fqn == NULL) {
+        debug_fqn = talloc_asprintf(tmp_ctx, "GID:%d@%s", input->id, domain->name);
+        if (debug_fqn == NULL) {
             ret = ENOMEM;
             goto done;
         }
@@ -196,7 +211,7 @@ cache_req_input_set_domain(struct cache_req_input *input,
 
     input->domain = domain;
     input->dom_objname = talloc_steal(input, name);
-    input->debug_fqn = talloc_steal(input, fqn);
+    input->debug_fqn = talloc_steal(input, debug_fqn);
 
     ret = EOK;
 
@@ -227,6 +242,10 @@ static errno_t cache_req_check_ncache(struct cache_req_input *input,
     case CACHE_REQ_GROUP_BY_ID:
         ret = sss_ncache_check_gid(ncache, neg_timeout, input->id);
         break;
+    case CACHE_REQ_USER_BY_FILTER:
+    case CACHE_REQ_GROUP_BY_FILTER:
+        ret = EOK;
+        break;
     }
 
     if (ret == EEXIST) {
@@ -252,6 +271,10 @@ static void cache_req_add_to_ncache(struct cache_req_input *input,
         ret = sss_ncache_set_group(ncache, false, input->domain,
                                    input->dom_objname);
         break;
+    case CACHE_REQ_USER_BY_FILTER:
+    case CACHE_REQ_GROUP_BY_FILTER:
+        /* Nothing to do, adding a wildcard request to cache doesn't
+         * make sense */
     case CACHE_REQ_USER_BY_ID:
     case CACHE_REQ_GROUP_BY_ID:
         /* Nothing to do. Those types must be unique among all domains so
@@ -277,6 +300,10 @@ static void cache_req_add_to_ncache_global(struct cache_req_input *input,
     errno_t ret = ERR_INTERNAL;
 
     switch (input->type) {
+    case CACHE_REQ_USER_BY_FILTER:
+    case CACHE_REQ_GROUP_BY_FILTER:
+        /* Nothing to do, adding a wildcard request to cache doesn't
+         * make sense */
     case CACHE_REQ_USER_BY_NAME:
     case CACHE_REQ_GROUP_BY_NAME:
     case CACHE_REQ_INITGROUPS:
@@ -302,6 +329,44 @@ static void cache_req_add_to_ncache_global(struct cache_req_input *input,
     return;
 }
 
+static errno_t updated_users_by_filter(TALLOC_CTX *mem_ctx,
+                                       struct sss_domain_info *domain,
+                                       const char *name_filter,
+                                       time_t since,
+                                       struct ldb_result **_res)
+{
+    int ret;
+    char *recent_filter;
+
+    recent_filter = talloc_asprintf(mem_ctx, "(%s>=%lu)",
+                                    SYSDB_LAST_UPDATE, since);
+    ret = sysdb_enumpwent_filter_with_views(mem_ctx, domain,
+                                            name_filter, recent_filter,
+                                            _res);
+    talloc_free(recent_filter);
+
+    return ret;
+}
+
+static errno_t updated_groups_by_filter(TALLOC_CTX *mem_ctx,
+                                        struct sss_domain_info *domain,
+                                        const char *name_filter,
+                                        time_t since,
+                                        struct ldb_result **_res)
+{
+    int ret;
+    char *recent_filter;
+
+    recent_filter = talloc_asprintf(mem_ctx, "(%s>=%lu)",
+                                    SYSDB_LAST_UPDATE, since);
+    ret = sysdb_enumgrent_filter_with_views(mem_ctx, domain,
+                                            name_filter, recent_filter,
+                                            _res);
+    talloc_free(recent_filter);
+
+    return ret;
+}
+
 static errno_t cache_req_get_object(TALLOC_CTX *mem_ctx,
                                     struct cache_req_input *input,
                                     struct ldb_result **_result)
@@ -338,6 +403,18 @@ static errno_t cache_req_get_object(TALLOC_CTX *mem_ctx,
         ret = sysdb_initgroups_with_views(mem_ctx, input->domain,
                                           input->dom_objname, &result);
         break;
+    case CACHE_REQ_USER_BY_FILTER:
+        one_item_only = false;
+        ret = updated_users_by_filter(mem_ctx, input->domain,
+                                      input->dom_objname, input->req_start,
+                                      &result);
+        break;
+    case CACHE_REQ_GROUP_BY_FILTER:
+        one_item_only = false;
+        ret = updated_groups_by_filter(mem_ctx, input->domain,
+                                       input->dom_objname, input->req_start,
+                                       &result);
+        break;
     }
 
     if (ret != EOK) {
@@ -464,7 +541,9 @@ static errno_t cache_req_cache_check(struct tevent_req *req)
 
     state = tevent_req_data(req, struct cache_req_cache_state);
 
-    if (state->result == NULL || state->result->count == 0) {
+    if (state->result == NULL || state->result->count == 0 ||
+            state->input->type == CACHE_REQ_USER_BY_FILTER ||
+            state->input->type == CACHE_REQ_GROUP_BY_FILTER) {
         ret = ENOENT;
     } else {
         if (state->input->type == CACHE_REQ_INITGROUPS) {
@@ -987,3 +1066,41 @@ cache_req_initgr_by_name_send(TALLOC_CTX *mem_ctx,
                                           neg_timeout, cache_refresh_percent,
                                           domain, input);
 }
+
+struct tevent_req *
+cache_req_user_by_filter_send(TALLOC_CTX *mem_ctx,
+                              struct tevent_context *ev,
+                              struct resp_ctx *rctx,
+                              const char *domain,
+                              const char *filter)
+{
+    struct cache_req_input *input;
+
+    input = cache_req_input_create(mem_ctx, CACHE_REQ_USER_BY_FILTER,
+                                   filter, 0);
+    if (input == NULL) {
+        return NULL;
+    }
+
+    return cache_req_steal_input_and_send(mem_ctx, ev, rctx, NULL,
+                                          0, 0, domain, input);
+}
+
+struct tevent_req *
+cache_req_group_by_filter_send(TALLOC_CTX *mem_ctx,
+                               struct tevent_context *ev,
+                               struct resp_ctx *rctx,
+                               const char *domain,
+                               const char *filter)
+{
+    struct cache_req_input *input;
+
+    input = cache_req_input_create(mem_ctx, CACHE_REQ_GROUP_BY_FILTER,
+                                   filter, 0);
+    if (input == NULL) {
+        return NULL;
+    }
+
+    return cache_req_steal_input_and_send(mem_ctx, ev, rctx, NULL,
+                                          0, 0, domain, input);
+}
diff --git a/src/responder/common/responder_cache_req.h b/src/responder/common/responder_cache_req.h
index 088e8efe0ceefe49510bc5eab37e7825b667e5f3..bc3a61851189df14c20e371edfd7341e41da6bcd 100644
--- a/src/responder/common/responder_cache_req.h
+++ b/src/responder/common/responder_cache_req.h
@@ -30,8 +30,10 @@
 enum cache_req_type {
     CACHE_REQ_USER_BY_NAME,
     CACHE_REQ_USER_BY_ID,
+    CACHE_REQ_USER_BY_FILTER,
     CACHE_REQ_GROUP_BY_NAME,
     CACHE_REQ_GROUP_BY_ID,
+    CACHE_REQ_GROUP_BY_FILTER,
     CACHE_REQ_INITGROUPS
 };
 
@@ -128,4 +130,24 @@ cache_req_initgr_by_name_send(TALLOC_CTX *mem_ctx,
 #define cache_req_initgr_by_name_recv(mem_ctx, req, _result, _domain, _name) \
     cache_req_recv(mem_ctx, req, _result, _domain, _name)
 
+struct tevent_req *
+cache_req_user_by_filter_send(TALLOC_CTX *mem_ctx,
+                              struct tevent_context *ev,
+                              struct resp_ctx *rctx,
+                              const char *domain,
+                              const char *filter);
+
+#define cache_req_user_by_filter_recv(mem_ctx, req, _result, _domain) \
+    cache_req_recv(mem_ctx, req, _result, _domain, NULL)
+
+struct tevent_req *
+cache_req_group_by_filter_send(TALLOC_CTX *mem_ctx,
+                              struct tevent_context *ev,
+                              struct resp_ctx *rctx,
+                              const char *domain,
+                              const char *filter);
+
+#define cache_req_group_by_filter_recv(mem_ctx, req, _result, _domain) \
+    cache_req_recv(mem_ctx, req, _result, _domain, NULL)
+
 #endif /* RESPONDER_CACHE_H_ */
diff --git a/src/tests/cmocka/test_responder_cache_req.c b/src/tests/cmocka/test_responder_cache_req.c
index e30deed1c6bc4023a4f2154db21fe1339e9bb3c5..31b6694668607815652f45bc93210554fd2ac918 100644
--- a/src/tests/cmocka/test_responder_cache_req.c
+++ b/src/tests/cmocka/test_responder_cache_req.c
@@ -38,6 +38,9 @@
 #define TEST_GROUP_NAME "test-group"
 #define TEST_GROUP_ID 1000
 
+#define TEST_USER_NAME2 "test-user2"
+#define TEST_GROUP_NAME2 "test-group2"
+
 #define new_single_domain_test(test) \
     cmocka_unit_test_setup_teardown(test_ ## test, \
                                     test_single_domain_setup, \
@@ -1694,6 +1697,405 @@ void test_group_by_id_missing_notfound(void **state)
     assert_true(test_ctx->dp_called);
 }
 
+static void cache_req_user_by_filter_test_done(struct tevent_req *req)
+{
+    struct cache_req_test_ctx *ctx = NULL;
+
+    ctx = tevent_req_callback_data(req, struct cache_req_test_ctx);
+
+    ctx->tctx->error = cache_req_user_by_filter_recv(ctx, req,
+                                                     &ctx->result,
+                                                     &ctx->domain);
+    talloc_zfree(req);
+    ctx->tctx->done = true;
+}
+
+void test_users_by_filter_valid(void **state)
+{
+    struct cache_req_test_ctx *test_ctx = NULL;
+    TALLOC_CTX *req_mem_ctx = NULL;
+    struct tevent_req *req = NULL;
+    const char *ldbname = NULL;
+    errno_t ret;
+
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
+    test_ctx->create_user = true;
+
+    ret = sysdb_store_user(test_ctx->tctx->dom, TEST_USER_NAME2, "pwd", 1001, 1001,
+                           NULL, NULL, NULL, "cn="TEST_USER_NAME2",dc=test", NULL,
+                           NULL, 1000, time(NULL));
+    assert_int_equal(ret, EOK);
+
+    req_mem_ctx = talloc_new(global_talloc_context);
+    check_leaks_push(req_mem_ctx);
+
+    /* Filters always go to DP */
+    will_return(__wrap_sss_dp_get_account_send, test_ctx);
+    mock_account_recv_simple();
+
+    req = cache_req_user_by_filter_send(req_mem_ctx, test_ctx->tctx->ev,
+                                        test_ctx->rctx,
+                                        test_ctx->tctx->dom->name,
+                                        "test*");
+    assert_non_null(req);
+    tevent_req_set_callback(req, cache_req_user_by_filter_test_done, test_ctx);
+
+    ret = test_ev_loop(test_ctx->tctx);
+    assert_int_equal(ret, ERR_OK);
+    assert_true(check_leaks_pop(req_mem_ctx));
+
+    assert_non_null(test_ctx->result);
+    assert_int_equal(test_ctx->result->count, 2);
+
+    ldbname = ldb_msg_find_attr_as_string(test_ctx->result->msgs[0],
+                                          SYSDB_NAME, NULL);
+    assert_non_null(ldbname);
+    assert_string_equal(ldbname, TEST_USER_NAME2);
+
+    ldbname = ldb_msg_find_attr_as_string(test_ctx->result->msgs[1],
+                                          SYSDB_NAME, NULL);
+    assert_non_null(ldbname);
+    assert_string_equal(ldbname, TEST_USER_NAME);
+}
+
+void test_users_by_filter_filter_old(void **state)
+{
+    struct cache_req_test_ctx *test_ctx = NULL;
+    TALLOC_CTX *req_mem_ctx = NULL;
+    struct tevent_req *req = NULL;
+    const char *ldbname = NULL;
+    errno_t ret;
+
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
+    test_ctx->create_user = true;
+
+    /* This user was updated in distant past, so it wont't be reported by
+     * the filter search */
+    ret = sysdb_store_user(test_ctx->tctx->dom, TEST_USER_NAME2, "pwd", 1001, 1001,
+                           NULL, NULL, NULL, "cn="TEST_USER_NAME2",dc=test", NULL,
+                           NULL, 1000, 1);
+    assert_int_equal(ret, EOK);
+
+    req_mem_ctx = talloc_new(global_talloc_context);
+    check_leaks_push(req_mem_ctx);
+
+    /* Filters always go to DP */
+    will_return(__wrap_sss_dp_get_account_send, test_ctx);
+    mock_account_recv_simple();
+
+    req = cache_req_user_by_filter_send(req_mem_ctx, test_ctx->tctx->ev,
+                                        test_ctx->rctx,
+                                        test_ctx->tctx->dom->name,
+                                        "test*");
+    assert_non_null(req);
+    tevent_req_set_callback(req, cache_req_user_by_filter_test_done, test_ctx);
+
+    ret = test_ev_loop(test_ctx->tctx);
+    assert_int_equal(ret, ERR_OK);
+    assert_true(check_leaks_pop(req_mem_ctx));
+
+    assert_non_null(test_ctx->result);
+    assert_int_equal(test_ctx->result->count, 1);
+
+    ldbname = ldb_msg_find_attr_as_string(test_ctx->result->msgs[0],
+                                          SYSDB_NAME, NULL);
+    assert_non_null(ldbname);
+    assert_string_equal(ldbname, TEST_USER_NAME);
+}
+
+void test_users_by_filter_notfound(void **state)
+{
+    struct cache_req_test_ctx *test_ctx = NULL;
+    TALLOC_CTX *req_mem_ctx = NULL;
+    struct tevent_req *req = NULL;
+    errno_t ret;
+
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
+
+    req_mem_ctx = talloc_new(global_talloc_context);
+    check_leaks_push(req_mem_ctx);
+
+    /* Filters always go to DP */
+    will_return(__wrap_sss_dp_get_account_send, test_ctx);
+    mock_account_recv_simple();
+
+    req = cache_req_user_by_filter_send(req_mem_ctx, test_ctx->tctx->ev,
+                                        test_ctx->rctx,
+                                        test_ctx->tctx->dom->name,
+                                        "nosuchuser*");
+    assert_non_null(req);
+    tevent_req_set_callback(req, cache_req_user_by_filter_test_done, test_ctx);
+
+    ret = test_ev_loop(test_ctx->tctx);
+    assert_int_equal(ret, ENOENT);
+    assert_true(check_leaks_pop(req_mem_ctx));
+}
+
+static void test_users_by_filter_multiple_domains_valid(void **state)
+{
+    struct cache_req_test_ctx *test_ctx = NULL;
+    struct sss_domain_info *domain = NULL;
+    TALLOC_CTX *req_mem_ctx = NULL;
+    struct tevent_req *req = NULL;
+    const char *ldbname = NULL;
+    errno_t ret;
+
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
+
+    domain = find_domain_by_name(test_ctx->tctx->dom,
+                                 "responder_cache_req_test_d", true);
+    assert_non_null(domain);
+
+    ret = sysdb_store_user(domain, TEST_USER_NAME, "pwd", 1000, 1000,
+                           NULL, NULL, NULL, "cn="TEST_USER_NAME",dc=test", NULL,
+                           NULL, 1000, time(NULL));
+    assert_int_equal(ret, EOK);
+
+    ret = sysdb_store_user(domain, TEST_USER_NAME2, "pwd", 1001, 1001,
+                           NULL, NULL, NULL, "cn="TEST_USER_NAME2",dc=test", NULL,
+                           NULL, 1000, time(NULL));
+    assert_int_equal(ret, EOK);
+
+    req_mem_ctx = talloc_new(global_talloc_context);
+    check_leaks_push(req_mem_ctx);
+
+    /* Filters always go to DP */
+    will_return(__wrap_sss_dp_get_account_send, test_ctx);
+    mock_account_recv_simple();
+
+    req = cache_req_user_by_filter_send(req_mem_ctx, test_ctx->tctx->ev,
+                                        test_ctx->rctx,
+                                        domain->name,
+                                        "test*");
+    assert_non_null(req);
+    tevent_req_set_callback(req, cache_req_user_by_filter_test_done, test_ctx);
+
+    ret = test_ev_loop(test_ctx->tctx);
+    assert_int_equal(ret, ERR_OK);
+    assert_true(check_leaks_pop(req_mem_ctx));
+
+    assert_non_null(test_ctx->result);
+    assert_int_equal(test_ctx->result->count, 2);
+
+    ldbname = ldb_msg_find_attr_as_string(test_ctx->result->msgs[0],
+                                          SYSDB_NAME, NULL);
+    assert_non_null(ldbname);
+    assert_string_equal(ldbname, TEST_USER_NAME2);
+
+    ldbname = ldb_msg_find_attr_as_string(test_ctx->result->msgs[1],
+                                          SYSDB_NAME, NULL);
+    assert_non_null(ldbname);
+    assert_string_equal(ldbname, TEST_USER_NAME);
+}
+
+static void test_users_by_filter_multiple_domains_notfound(void **state)
+{
+    struct cache_req_test_ctx *test_ctx = NULL;
+    struct sss_domain_info *domain = NULL;
+    TALLOC_CTX *req_mem_ctx = NULL;
+    struct tevent_req *req = NULL;
+    errno_t ret;
+
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
+
+    domain = find_domain_by_name(test_ctx->tctx->dom,
+                                 "responder_cache_req_test_d", true);
+    assert_non_null(domain);
+
+    req_mem_ctx = talloc_new(global_talloc_context);
+    check_leaks_push(req_mem_ctx);
+
+    /* Filters always go to DP */
+    will_return(__wrap_sss_dp_get_account_send, test_ctx);
+    mock_account_recv_simple();
+
+    req = cache_req_user_by_filter_send(req_mem_ctx, test_ctx->tctx->ev,
+                                        test_ctx->rctx,
+                                        domain->name,
+                                        "nosuchuser*");
+    assert_non_null(req);
+    tevent_req_set_callback(req, cache_req_user_by_filter_test_done, test_ctx);
+
+    ret = test_ev_loop(test_ctx->tctx);
+    assert_int_equal(ret, ENOENT);
+    assert_true(check_leaks_pop(req_mem_ctx));
+}
+
+static void cache_req_group_by_filter_test_done(struct tevent_req *req)
+{
+    struct cache_req_test_ctx *ctx = NULL;
+
+    ctx = tevent_req_callback_data(req, struct cache_req_test_ctx);
+
+    ctx->tctx->error = cache_req_group_by_filter_recv(ctx, req,
+                                                      &ctx->result,
+                                                      &ctx->domain);
+    talloc_zfree(req);
+    ctx->tctx->done = true;
+}
+
+void test_groups_by_filter_valid(void **state)
+{
+    struct cache_req_test_ctx *test_ctx = NULL;
+    TALLOC_CTX *req_mem_ctx = NULL;
+    struct tevent_req *req = NULL;
+    const char *ldbname = NULL;
+    errno_t ret;
+
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
+    test_ctx->create_group = true;
+
+    ret = sysdb_store_group(test_ctx->tctx->dom, TEST_GROUP_NAME2,
+                            1001, NULL, 1001, time(NULL));
+    assert_int_equal(ret, EOK);
+
+    req_mem_ctx = talloc_new(global_talloc_context);
+    check_leaks_push(req_mem_ctx);
+
+    /* Filters always go to DP */
+    will_return(__wrap_sss_dp_get_account_send, test_ctx);
+    mock_account_recv_simple();
+
+    req = cache_req_group_by_filter_send(req_mem_ctx, test_ctx->tctx->ev,
+                                         test_ctx->rctx,
+                                         test_ctx->tctx->dom->name,
+                                         "test*");
+    assert_non_null(req);
+    tevent_req_set_callback(req, cache_req_group_by_filter_test_done, test_ctx);
+
+    ret = test_ev_loop(test_ctx->tctx);
+    assert_int_equal(ret, ERR_OK);
+    assert_true(check_leaks_pop(req_mem_ctx));
+
+    assert_non_null(test_ctx->result);
+    assert_int_equal(test_ctx->result->count, 2);
+
+    ldbname = ldb_msg_find_attr_as_string(test_ctx->result->msgs[0],
+                                          SYSDB_NAME, NULL);
+    assert_non_null(ldbname);
+    assert_string_equal(ldbname, TEST_GROUP_NAME2);
+
+    ldbname = ldb_msg_find_attr_as_string(test_ctx->result->msgs[1],
+                                          SYSDB_NAME, NULL);
+    assert_non_null(ldbname);
+    assert_string_equal(ldbname, TEST_GROUP_NAME);
+}
+
+void test_groups_by_filter_notfound(void **state)
+{
+    struct cache_req_test_ctx *test_ctx = NULL;
+    TALLOC_CTX *req_mem_ctx = NULL;
+    struct tevent_req *req = NULL;
+    errno_t ret;
+
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
+
+    req_mem_ctx = talloc_new(global_talloc_context);
+    check_leaks_push(req_mem_ctx);
+
+    /* Filters always go to DP */
+    will_return(__wrap_sss_dp_get_account_send, test_ctx);
+    mock_account_recv_simple();
+
+    req = cache_req_group_by_filter_send(req_mem_ctx, test_ctx->tctx->ev,
+                                        test_ctx->rctx,
+                                        test_ctx->tctx->dom->name,
+                                        "nosuchgroup*");
+    assert_non_null(req);
+    tevent_req_set_callback(req, cache_req_group_by_filter_test_done, test_ctx);
+
+    ret = test_ev_loop(test_ctx->tctx);
+    assert_int_equal(ret, ENOENT);
+    assert_true(check_leaks_pop(req_mem_ctx));
+}
+
+void test_groups_by_filter_multiple_domains_valid(void **state)
+{
+    struct cache_req_test_ctx *test_ctx = NULL;
+    struct sss_domain_info *domain = NULL;
+    TALLOC_CTX *req_mem_ctx = NULL;
+    struct tevent_req *req = NULL;
+    const char *ldbname = NULL;
+    errno_t ret;
+
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
+
+    domain = find_domain_by_name(test_ctx->tctx->dom,
+                                 "responder_cache_req_test_d", true);
+    assert_non_null(domain);
+
+    ret = sysdb_store_group(domain, TEST_GROUP_NAME,
+                            1000, NULL, 1000, time(NULL));
+    assert_int_equal(ret, EOK);
+
+    ret = sysdb_store_group(domain, TEST_GROUP_NAME2,
+                            1001, NULL, 1001, time(NULL));
+    assert_int_equal(ret, EOK);
+
+    req_mem_ctx = talloc_new(global_talloc_context);
+    check_leaks_push(req_mem_ctx);
+
+    /* Filters always go to DP */
+    will_return(__wrap_sss_dp_get_account_send, test_ctx);
+    mock_account_recv_simple();
+
+    req = cache_req_group_by_filter_send(req_mem_ctx, test_ctx->tctx->ev,
+                                         test_ctx->rctx,
+                                         domain->name,
+                                         "test*");
+    assert_non_null(req);
+    tevent_req_set_callback(req, cache_req_group_by_filter_test_done, test_ctx);
+
+    ret = test_ev_loop(test_ctx->tctx);
+    assert_int_equal(ret, ERR_OK);
+    assert_true(check_leaks_pop(req_mem_ctx));
+
+    assert_non_null(test_ctx->result);
+    assert_int_equal(test_ctx->result->count, 2);
+
+    ldbname = ldb_msg_find_attr_as_string(test_ctx->result->msgs[0],
+                                          SYSDB_NAME, NULL);
+    assert_non_null(ldbname);
+    assert_string_equal(ldbname, TEST_GROUP_NAME2);
+
+    ldbname = ldb_msg_find_attr_as_string(test_ctx->result->msgs[1],
+                                          SYSDB_NAME, NULL);
+    assert_non_null(ldbname);
+    assert_string_equal(ldbname, TEST_GROUP_NAME);
+}
+
+void test_groups_by_filter_multiple_domains_notfound(void **state)
+{
+    struct cache_req_test_ctx *test_ctx = NULL;
+    struct sss_domain_info *domain = NULL;
+    TALLOC_CTX *req_mem_ctx = NULL;
+    struct tevent_req *req = NULL;
+    errno_t ret;
+
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
+    domain = find_domain_by_name(test_ctx->tctx->dom,
+                                 "responder_cache_req_test_d", true);
+    assert_non_null(domain);
+
+    req_mem_ctx = talloc_new(global_talloc_context);
+    check_leaks_push(req_mem_ctx);
+
+    /* Filters always go to DP */
+    will_return(__wrap_sss_dp_get_account_send, test_ctx);
+    mock_account_recv_simple();
+
+    req = cache_req_group_by_filter_send(req_mem_ctx, test_ctx->tctx->ev,
+                                        test_ctx->rctx,
+                                        domain->name,
+                                        "nosuchgroup*");
+    assert_non_null(req);
+    tevent_req_set_callback(req, cache_req_group_by_filter_test_done, test_ctx);
+
+    ret = test_ev_loop(test_ctx->tctx);
+    assert_int_equal(ret, ENOENT);
+    assert_true(check_leaks_pop(req_mem_ctx));
+}
+
 int main(int argc, const char *argv[])
 {
     poptContext pc;
@@ -1741,7 +2143,17 @@ int main(int argc, const char *argv[])
         new_single_domain_test(group_by_id_missing_found),
         new_single_domain_test(group_by_id_missing_notfound),
         new_multi_domain_test(group_by_id_multiple_domains_found),
-        new_multi_domain_test(group_by_id_multiple_domains_notfound)
+        new_multi_domain_test(group_by_id_multiple_domains_notfound),
+
+        new_single_domain_test(users_by_filter_valid),
+        new_single_domain_test(users_by_filter_filter_old),
+        new_single_domain_test(users_by_filter_notfound),
+        new_multi_domain_test(users_by_filter_multiple_domains_valid),
+        new_multi_domain_test(users_by_filter_multiple_domains_notfound),
+        new_single_domain_test(groups_by_filter_valid),
+        new_single_domain_test(groups_by_filter_notfound),
+        new_multi_domain_test(groups_by_filter_multiple_domains_valid),
+        new_multi_domain_test(groups_by_filter_multiple_domains_notfound),
     };
 
     /* Set debug level to invalid value so we can deside if -d 0 was used. */
-- 
2.4.3

-------------- next part --------------
>From 51374e9018f28ea27637982c35f935e2312affd9 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek at redhat.com>
Date: Mon, 4 May 2015 13:10:01 +0200
Subject: [PATCH 05/11] UTIL: Add sss_filter_sanitize_ex

Related:
    https://fedorahosted.org/sssd/ticket/2553

In order to support wildcard request, we need to introduce an optionally
relaxed version of sss_filter_sanitize that allows to select which
characters are exempt from sanitizing.
---
 src/tests/util-tests.c |  8 ++++++++
 src/util/util.c        | 28 +++++++++++++++++++++++++---
 src/util/util.h        |  5 +++++
 3 files changed, 38 insertions(+), 3 deletions(-)

diff --git a/src/tests/util-tests.c b/src/tests/util-tests.c
index 3d42f0193a677200d5cb4a46805892bed978305c..abfb7565da47db6df8cba3ee6dfd72dc6d2d2f46 100644
--- a/src/tests/util-tests.c
+++ b/src/tests/util-tests.c
@@ -406,6 +406,14 @@ START_TEST(test_sss_filter_sanitize)
                 "Expected [%s], got [%s]",
                 has_all_expected, sanitized);
 
+    const char has_all_allow_asterisk_expected[] = "\\5c\\28user\\29*name";
+    ret = sss_filter_sanitize_ex(test_ctx, has_all, &sanitized, "*");
+    fail_unless(ret == EOK, "has_all error [%d][%s]",
+                ret, strerror(ret));
+    fail_unless(strcmp(has_all_allow_asterisk_expected, sanitized)==0,
+                "Expected [%s], got [%s]",
+                has_all_expected, sanitized);
+
     talloc_free(test_ctx);
 }
 END_TEST
diff --git a/src/util/util.c b/src/util/util.c
index cfd26a58b31048996e9669163b821282b219b2de..782cd026b7928e607a8980fb5f333c794feb5b1a 100644
--- a/src/util/util.c
+++ b/src/util/util.c
@@ -525,13 +525,15 @@ errno_t sss_hash_create(TALLOC_CTX *mem_ctx, unsigned long count,
     return sss_hash_create_ex(mem_ctx, count, tbl, 0, 0, 0, 0, NULL, NULL);
 }
 
-errno_t sss_filter_sanitize(TALLOC_CTX *mem_ctx,
-                            const char *input,
-                            char **sanitized)
+errno_t sss_filter_sanitize_ex(TALLOC_CTX *mem_ctx,
+                               const char *input,
+                               char **sanitized,
+                               const char *ignore)
 {
     char *output;
     size_t i = 0;
     size_t j = 0;
+    char *allowed;
 
     /* Assume the worst-case. We'll resize it later, once */
     output = talloc_array(mem_ctx, char, strlen(input) * 3 + 1);
@@ -540,6 +542,19 @@ errno_t sss_filter_sanitize(TALLOC_CTX *mem_ctx,
     }
 
     while (input[i]) {
+        /* Even though this character might have a special meaning, if it's
+         * expliticly allowed, just copy it and move on
+         */
+        if (ignore == NULL) {
+            allowed = NULL;
+        } else {
+            allowed = strchr(ignore, input[i]);
+        }
+        if (allowed) {
+            output[j++] = input[i++];
+            continue;
+        }
+
         switch(input[i]) {
         case '\t':
             output[j++] = '\\';
@@ -587,6 +602,13 @@ errno_t sss_filter_sanitize(TALLOC_CTX *mem_ctx,
     return EOK;
 }
 
+errno_t sss_filter_sanitize(TALLOC_CTX *mem_ctx,
+                            const char *input,
+                            char **sanitized)
+{
+    return sss_filter_sanitize_ex(mem_ctx, input, sanitized, NULL);
+}
+
 char *
 sss_escape_ip_address(TALLOC_CTX *mem_ctx, int family, const char *addr)
 {
diff --git a/src/util/util.h b/src/util/util.h
index 786ed303e1d84dd2783295c51c6126b17c344e5a..a57cf8496b1a20aef698ad5347626fc4927064d4 100644
--- a/src/util/util.h
+++ b/src/util/util.h
@@ -485,6 +485,11 @@ errno_t sss_filter_sanitize(TALLOC_CTX *mem_ctx,
                             const char *input,
                             char **sanitized);
 
+errno_t sss_filter_sanitize_ex(TALLOC_CTX *mem_ctx,
+                               const char *input,
+                               char **sanitized,
+                               const char *ignore);
+
 errno_t sss_filter_sanitize_for_dom(TALLOC_CTX *mem_ctx,
                                     const char *input,
                                     struct sss_domain_info *dom,
-- 
2.4.3

-------------- next part --------------
>From aee4ba40fbcb7a67288278a4d6c0ba06684ee4f8 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek at redhat.com>
Date: Mon, 4 May 2015 15:16:44 +0200
Subject: [PATCH 06/11] LDAP: Fetch users and groups using wildcards

Related:
    https://fedorahosted.org/sssd/ticket/2553

Adds handler for the BE_FILTER_WILDCARD in the LDAP provider. So far
it's the same code as if enumeration was used, so there are no limits.
---
 src/providers/ldap/ldap_common.h |  3 +++
 src/providers/ldap/ldap_id.c     | 54 ++++++++++++++++++++++++++++++++++++++--
 2 files changed, 55 insertions(+), 2 deletions(-)

diff --git a/src/providers/ldap/ldap_common.h b/src/providers/ldap/ldap_common.h
index 424eacb1da0a6934b132ccb2a5bb175233fa1a80..8294d1db23bdca8d94a098533d93405c4d55226b 100644
--- a/src/providers/ldap/ldap_common.h
+++ b/src/providers/ldap/ldap_common.h
@@ -39,6 +39,9 @@
 #define LDAP_SSL_URI "ldaps://"
 #define LDAP_LDAPI_URI "ldapi://"
 
+/* Only the asterisk is allowed in wildcard requests */
+#define LDAP_ALLOWED_WILDCARDS "*"
+
 /* a fd the child process would log into */
 extern int ldap_child_debug_fd;
 
diff --git a/src/providers/ldap/ldap_id.c b/src/providers/ldap/ldap_id.c
index d4f46f1dcd69898e5883d21be07f895fd198cf50..f3e829a1dab450bdaf1afc5d83cbff5e0aaeef1d 100644
--- a/src/providers/ldap/ldap_id.c
+++ b/src/providers/ldap/ldap_id.c
@@ -48,6 +48,7 @@ struct users_get_state {
 
     const char *name;
     int filter_type;
+    time_t req_start;
 
     char *filter;
     const char **attrs;
@@ -112,6 +113,14 @@ struct tevent_req *users_get_send(TALLOC_CTX *memctx,
                                                           sdom->dom->name,
                                                           sdom->dom->domain_id);
     switch (filter_type) {
+    case BE_FILTER_WILDCARD:
+        attr_name = ctx->opts->user_map[SDAP_AT_USER_NAME].name;
+        ret = sss_filter_sanitize_ex(state, name, &clean_name,
+                                     LDAP_ALLOWED_WILDCARDS);
+        if (ret != EOK) {
+            goto done;
+        }
+        break;
     case BE_FILTER_NAME:
         if (extra_value && strcmp(extra_value, EXTRA_NAME_IS_UPN) == 0) {
             attr_name = ctx->opts->user_map[SDAP_AT_USER_PRINC].name;
@@ -359,6 +368,14 @@ static void users_get_search(struct tevent_req *req)
     struct users_get_state *state = tevent_req_data(req,
                                                      struct users_get_state);
     struct tevent_req *subreq;
+    bool multiple_results;
+
+    if (state->filter_type == BE_FILTER_WILDCARD) {
+        multiple_results = true;
+        state->req_start = time(NULL);
+    } else {
+        multiple_results = false;
+    }
 
     subreq = sdap_get_users_send(state, state->ev,
                                  state->domain, state->sysdb,
@@ -368,7 +385,7 @@ static void users_get_search(struct tevent_req *req)
                                  state->attrs, state->filter,
                                  dp_opt_get_int(state->ctx->opts->basic,
                                                 SDAP_SEARCH_TIMEOUT),
-                                 false);
+                                 multiple_results);
     if (!subreq) {
         tevent_req_error(req, ENOMEM);
         return;
@@ -479,6 +496,13 @@ static void users_get_done(struct tevent_req *subreq)
              * group we have nothing to do here. */
             break;
 
+        case BE_FILTER_WILDCARD:
+            /* We can't know if all users are up-to-date, especially in a large
+             * environment. Do not delete any records, let the responder fetch
+             * the entries they are requested in
+             */
+            break;
+
         default:
             tevent_req_error(req, EINVAL);
             return;
@@ -521,6 +545,7 @@ struct groups_get_state {
 
     const char *name;
     int filter_type;
+    time_t req_start;
 
     char *filter;
     const char **attrs;
@@ -590,6 +615,14 @@ struct tevent_req *groups_get_send(TALLOC_CTX *memctx,
                                                           sdom->dom->domain_id);
 
     switch(filter_type) {
+    case BE_FILTER_WILDCARD:
+        attr_name = ctx->opts->group_map[SDAP_AT_GROUP_NAME].name;
+        ret = sss_filter_sanitize_ex(state, name, &clean_name,
+                                     LDAP_ALLOWED_WILDCARDS);
+        if (ret != EOK) {
+            goto done;
+        }
+        break;
     case BE_FILTER_NAME:
         attr_name = ctx->opts->group_map[SDAP_AT_GROUP_NAME].name;
 
@@ -842,6 +875,14 @@ static void groups_get_search(struct tevent_req *req)
     struct groups_get_state *state = tevent_req_data(req,
                                                      struct groups_get_state);
     struct tevent_req *subreq;
+    bool multiple_results;
+
+    if (state->filter_type == BE_FILTER_WILDCARD) {
+        multiple_results = true;
+        state->req_start = time(NULL);
+    } else {
+        multiple_results = false;
+    }
 
     subreq = sdap_get_groups_send(state, state->ev,
                                   state->sdom,
@@ -850,7 +891,8 @@ static void groups_get_search(struct tevent_req *req)
                                   state->attrs, state->filter,
                                   dp_opt_get_int(state->ctx->opts->basic,
                                                  SDAP_SEARCH_TIMEOUT),
-                                  false, state->no_members);
+                                  multiple_results,
+                                  state->no_members);
     if (!subreq) {
         tevent_req_error(req, ENOMEM);
         return;
@@ -924,6 +966,14 @@ static void groups_get_done(struct tevent_req *subreq)
              * group we have nothing to do here. */
             break;
 
+        case BE_FILTER_WILDCARD:
+            /* We can't know if all users are up-to-date, especially in a large
+             * environment. Do not delete any records, let the responder fetch
+             * the entries they are requested in
+             */
+            break;
+
+
         default:
             tevent_req_error(req, EINVAL);
             return;
-- 
2.4.3

-------------- next part --------------
>From 9eec3341f39816f85707d535c6744af71d0178eb Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek at redhat.com>
Date: Mon, 4 May 2015 16:27:06 +0200
Subject: [PATCH 07/11] LDAP: Add sdap_get_and_parse_generic_send

Related:
    https://fedorahosted.org/sssd/ticket/2553

So far we had a simple sdap_get_generic_send() request that uses the
right defaults around the low-level sdap_get_generic_ext_send() request
and calls the parser.

This patch adds also sdap_get_and_parse_generic_send() that exposes all
options that sdap_get_generic_ext_send() offers but also calls the
parser.

In this patch the function is not used at all.
---
 src/providers/ldap/sdap_async.c | 185 ++++++++++++++++++++++++++++------------
 src/providers/ldap/sdap_async.h |  22 +++++
 2 files changed, 154 insertions(+), 53 deletions(-)

diff --git a/src/providers/ldap/sdap_async.c b/src/providers/ldap/sdap_async.c
index 5263c250b8a15a98e685ca81e636def27ea05894..c5be8561a197c96f62bb2582a4d30c28de71f580 100644
--- a/src/providers/ldap/sdap_async.c
+++ b/src/providers/ldap/sdap_async.c
@@ -1656,9 +1656,8 @@ static void generic_ext_search_handler(struct tevent_req *subreq,
     tevent_req_done(req);
 }
 
-
-/* ==Generic Search============================================ */
-struct sdap_get_generic_state {
+/* ==Generic Search exposing all options======================= */
+struct sdap_get_and_parse_generic_state {
     struct sdap_attr_map *map;
     int map_num_attrs;
 
@@ -1666,10 +1665,119 @@ struct sdap_get_generic_state {
     struct sdap_options *opts;
 };
 
+static void sdap_get_and_parse_generic_done(struct tevent_req *subreq);
+static errno_t sdap_get_and_parse_generic_parse_entry(struct sdap_handle *sh,
+                                                      struct sdap_msg *msg,
+                                                      void *pvt);
+
+struct tevent_req *sdap_get_and_parse_generic_send(TALLOC_CTX *memctx,
+                                                   struct tevent_context *ev,
+                                                   struct sdap_options *opts,
+                                                   struct sdap_handle *sh,
+                                                   const char *search_base,
+                                                   int scope,
+                                                   const char *filter,
+                                                   const char **attrs,
+                                                   struct sdap_attr_map *map,
+                                                   int map_num_attrs,
+                                                   int attrsonly,
+                                                   LDAPControl **serverctrls,
+                                                   LDAPControl **clientctrls,
+                                                   int sizelimit,
+                                                   int timeout,
+                                                   bool allow_paging)
+{
+    struct tevent_req *req = NULL;
+    struct tevent_req *subreq = NULL;
+    struct sdap_get_and_parse_generic_state *state = NULL;
+
+    req = tevent_req_create(memctx, &state,
+                            struct sdap_get_and_parse_generic_state);
+    if (!req) return NULL;
+
+    state->map = map;
+    state->map_num_attrs = map_num_attrs;
+    state->opts = opts;
+
+    subreq = sdap_get_generic_ext_send(state, ev, opts, sh, search_base,
+                                       scope, filter, attrs, false, NULL,
+                                       NULL, sizelimit, timeout, allow_paging,
+                                       sdap_get_and_parse_generic_parse_entry, state);
+    if (!subreq) {
+        talloc_zfree(req);
+        return NULL;
+    }
+    tevent_req_set_callback(subreq, sdap_get_and_parse_generic_done, req);
+
+    return req;
+}
+
+static errno_t sdap_get_and_parse_generic_parse_entry(struct sdap_handle *sh,
+                                                      struct sdap_msg *msg,
+                                                      void *pvt)
+{
+    errno_t ret;
+    struct sysdb_attrs *attrs;
+    struct sdap_get_and_parse_generic_state *state =
+                talloc_get_type(pvt, struct sdap_get_and_parse_generic_state);
+
+    bool disable_range_rtrvl = dp_opt_get_bool(state->opts->basic,
+                                               SDAP_DISABLE_RANGE_RETRIEVAL);
+
+    ret = sdap_parse_entry(state, sh, msg,
+                           state->map, state->map_num_attrs,
+                           &attrs, disable_range_rtrvl);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              "sdap_parse_entry failed [%d]: %s\n", ret, strerror(ret));
+        return ret;
+    }
+
+    ret = add_to_reply(state, &state->sreply, attrs);
+    if (ret != EOK) {
+        talloc_free(attrs);
+        DEBUG(SSSDBG_CRIT_FAILURE, "add_to_reply failed.\n");
+        return ret;
+    }
+
+    /* add_to_reply steals attrs, no need to free them here */
+    return EOK;
+}
+
+static void sdap_get_and_parse_generic_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct sdap_get_and_parse_generic_state *state =
+                tevent_req_data(req, struct sdap_get_and_parse_generic_state);
+
+    return generic_ext_search_handler(subreq, state->opts);
+}
+
+int sdap_get_and_parse_generic_recv(struct tevent_req *req,
+                                    TALLOC_CTX *mem_ctx,
+                                    size_t *reply_count,
+                                    struct sysdb_attrs ***reply)
+{
+    struct sdap_get_and_parse_generic_state *state = tevent_req_data(req,
+                                     struct sdap_get_and_parse_generic_state);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    *reply_count = state->sreply.reply_count;
+    *reply = talloc_steal(mem_ctx, state->sreply.reply);
+
+    return EOK;
+}
+
+
+/* ==Simple generic search============================================== */
+struct sdap_get_generic_state {
+    size_t reply_count;
+    struct sysdb_attrs **reply;
+};
+
 static void sdap_get_generic_done(struct tevent_req *subreq);
-static errno_t sdap_get_generic_parse_entry(struct sdap_handle *sh,
-                                            struct sdap_msg *msg,
-                                            void *pvt);
 
 struct tevent_req *sdap_get_generic_send(TALLOC_CTX *memctx,
                                          struct tevent_context *ev,
@@ -1691,16 +1799,12 @@ struct tevent_req *sdap_get_generic_send(TALLOC_CTX *memctx,
     req = tevent_req_create(memctx, &state, struct sdap_get_generic_state);
     if (!req) return NULL;
 
-    state->map = map;
-    state->map_num_attrs = map_num_attrs;
-    state->opts = opts;
-
-    subreq = sdap_get_generic_ext_send(state, ev, opts, sh, search_base,
-                                       scope, filter, attrs, false, NULL,
-                                       NULL, 0, timeout, allow_paging,
-                                       sdap_get_generic_parse_entry, state);
-    if (!subreq) {
-        talloc_zfree(req);
+    subreq = sdap_get_and_parse_generic_send(memctx, ev, opts, sh, search_base,
+                                             scope, filter, attrs,
+                                             map, map_num_attrs,
+                                             false, NULL, NULL, 0, timeout,
+                                             allow_paging);
+    if (subreq == NULL) {
         return NULL;
     }
     tevent_req_set_callback(subreq, sdap_get_generic_done, req);
@@ -1708,46 +1812,21 @@ struct tevent_req *sdap_get_generic_send(TALLOC_CTX *memctx,
     return req;
 }
 
-static errno_t sdap_get_generic_parse_entry(struct sdap_handle *sh,
-                                            struct sdap_msg *msg,
-                                            void *pvt)
-{
-    errno_t ret;
-    struct sysdb_attrs *attrs;
-    struct sdap_get_generic_state *state =
-                talloc_get_type(pvt, struct sdap_get_generic_state);
-
-    bool disable_range_rtrvl = dp_opt_get_bool(state->opts->basic,
-                                               SDAP_DISABLE_RANGE_RETRIEVAL);
-
-    ret = sdap_parse_entry(state, sh, msg,
-                           state->map, state->map_num_attrs,
-                           &attrs, disable_range_rtrvl);
-    if (ret != EOK) {
-        DEBUG(SSSDBG_MINOR_FAILURE,
-              "sdap_parse_entry failed [%d]: %s\n", ret, strerror(ret));
-        return ret;
-    }
-
-    ret = add_to_reply(state, &state->sreply, attrs);
-    if (ret != EOK) {
-        talloc_free(attrs);
-        DEBUG(SSSDBG_CRIT_FAILURE, "add_to_reply failed.\n");
-        return ret;
-    }
-
-    /* add_to_reply steals attrs, no need to free them here */
-    return EOK;
-}
-
 static void sdap_get_generic_done(struct tevent_req *subreq)
 {
     struct tevent_req *req = tevent_req_callback_data(subreq,
                                                       struct tevent_req);
     struct sdap_get_generic_state *state =
                 tevent_req_data(req, struct sdap_get_generic_state);
+    errno_t ret;
 
-    return generic_ext_search_handler(subreq, state->opts);
+    ret = sdap_get_and_parse_generic_recv(subreq, state,
+                                          &state->reply_count, &state->reply);
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        return;
+    }
+    tevent_req_done(req);
 }
 
 int sdap_get_generic_recv(struct tevent_req *req,
@@ -1755,13 +1834,13 @@ int sdap_get_generic_recv(struct tevent_req *req,
                           size_t *reply_count,
                           struct sysdb_attrs ***reply)
 {
-    struct sdap_get_generic_state *state = tevent_req_data(req,
-                                            struct sdap_get_generic_state);
+    struct sdap_get_generic_state *state =
+                tevent_req_data(req, struct sdap_get_generic_state);
 
     TEVENT_REQ_RETURN_ON_ERROR(req);
 
-    *reply_count = state->sreply.reply_count;
-    *reply = talloc_steal(mem_ctx, state->sreply.reply);
+    *reply_count = state->reply_count;
+    *reply = talloc_steal(mem_ctx, state->reply);
 
     return EOK;
 }
diff --git a/src/providers/ldap/sdap_async.h b/src/providers/ldap/sdap_async.h
index b5db64d7fa3c852fba60e07db19e823e818d29f3..b23dfc313905d01caedd1eace6bcb525481b9ebe 100644
--- a/src/providers/ldap/sdap_async.h
+++ b/src/providers/ldap/sdap_async.h
@@ -181,6 +181,28 @@ int sdap_cli_connect_recv(struct tevent_req *req,
                           struct sdap_handle **gsh,
                           struct sdap_server_opts **srv_opts);
 
+/* Exposes all options of generic send while allowing to parse by map */
+struct tevent_req *sdap_get_and_parse_generic_send(TALLOC_CTX *memctx,
+                                                   struct tevent_context *ev,
+                                                   struct sdap_options *opts,
+                                                   struct sdap_handle *sh,
+                                                   const char *search_base,
+                                                   int scope,
+                                                   const char *filter,
+                                                   const char **attrs,
+                                                   struct sdap_attr_map *map,
+                                                   int map_num_attrs,
+                                                   int attrsonly,
+                                                   LDAPControl **serverctrls,
+                                                   LDAPControl **clientctrls,
+                                                   int sizelimit,
+                                                   int timeout,
+                                                   bool allow_paging);
+int sdap_get_and_parse_generic_recv(struct tevent_req *req,
+                                    TALLOC_CTX *mem_ctx,
+                                    size_t *reply_count,
+                                    struct sysdb_attrs ***reply);
+
 struct tevent_req *sdap_get_generic_send(TALLOC_CTX *memctx,
                                          struct tevent_context *ev,
                                          struct sdap_options *opts,
-- 
2.4.3

-------------- next part --------------
>From d5f529164a1487f3f43ebaee13256efd8f48b8ce Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek at redhat.com>
Date: Mon, 4 May 2015 16:33:37 +0200
Subject: [PATCH 08/11] LDAP: Use sdap_get_and_parse_generic_/_recv

Related:
    https://fedorahosted.org/sssd/ticket/2553

Using the new request sdap_get_and_parse_generic_send is a separate
commit so that we can audit where the function is used during a code
review.
---
 src/providers/ldap/sdap_async_groups.c | 8 ++++----
 src/providers/ldap/sdap_async_users.c  | 8 ++++----
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/src/providers/ldap/sdap_async_groups.c b/src/providers/ldap/sdap_async_groups.c
index 73caa9b5bd662477cd29b5220f6b437991831578..e785307e60d0df5be96a5b2de2c07baabaf1e371 100644
--- a/src/providers/ldap/sdap_async_groups.c
+++ b/src/providers/ldap/sdap_async_groups.c
@@ -1870,14 +1870,14 @@ static errno_t sdap_get_groups_next_base(struct tevent_req *req)
           "Searching for groups with base [%s]\n",
            state->search_bases[state->base_iter]->basedn);
 
-    subreq = sdap_get_generic_send(
+    subreq = sdap_get_and_parse_generic_send(
             state, state->ev, state->opts,
             state->ldap_sh != NULL ? state->ldap_sh : state->sh,
             state->search_bases[state->base_iter]->basedn,
             state->search_bases[state->base_iter]->scope,
             state->filter, state->attrs,
             state->opts->group_map, SDAP_OPTS_GROUP,
-            state->timeout,
+            0, NULL, NULL, 0, state->timeout,
             state->enumeration); /* If we're enumerating, we need paging */
     if (!subreq) {
         return ENOMEM;
@@ -1903,8 +1903,8 @@ static void sdap_get_groups_process(struct tevent_req *subreq)
     struct sysdb_attrs **groups;
     char **groupnamelist;
 
-    ret = sdap_get_generic_recv(subreq, state,
-                                &count, &groups);
+    ret = sdap_get_and_parse_generic_recv(subreq, state,
+                                          &count, &groups);
     talloc_zfree(subreq);
     if (ret) {
         tevent_req_error(req, ret);
diff --git a/src/providers/ldap/sdap_async_users.c b/src/providers/ldap/sdap_async_users.c
index 416bedda8491fae5385e6b6a074b4cf05ae86b65..216b49477bf21481265444c5c03df0aac7ee84e4 100644
--- a/src/providers/ldap/sdap_async_users.c
+++ b/src/providers/ldap/sdap_async_users.c
@@ -688,13 +688,13 @@ static errno_t sdap_search_user_next_base(struct tevent_req *req)
           "Searching for users with base [%s]\n",
            state->search_bases[state->base_iter]->basedn);
 
-    subreq = sdap_get_generic_send(
+    subreq = sdap_get_and_parse_generic_send(
             state, state->ev, state->opts, state->sh,
             state->search_bases[state->base_iter]->basedn,
             state->search_bases[state->base_iter]->scope,
             state->filter, state->attrs,
             state->opts->user_map, state->opts->user_map_cnt,
-            state->timeout,
+            0, NULL, NULL, 0, state->timeout,
             state->enumeration); /* If we're enumerating, we need paging */
     if (subreq == NULL) {
         return ENOMEM;
@@ -715,8 +715,8 @@ static void sdap_search_user_process(struct tevent_req *subreq)
     struct sysdb_attrs **users;
     bool next_base = false;
 
-    ret = sdap_get_generic_recv(subreq, state,
-                                &count, &users);
+    ret = sdap_get_and_parse_generic_recv(subreq, state,
+                                          &count, &users);
     talloc_zfree(subreq);
     if (ret) {
         tevent_req_error(req, ret);
-- 
2.4.3

-------------- next part --------------
>From 1846a4510a88bc3e93ebb29ed113c921867b31ff Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek at redhat.com>
Date: Fri, 8 May 2015 14:49:09 +0200
Subject: [PATCH 09/11] LDAP: Add sdap_lookup_type enum

Related:
    https://fedorahosted.org/sssd/ticket/2553

Change the boolan parameter of sdap_get_users_send and sdap_get_groups_send
to a tri-state that controls whether we expect only a single entry
(ie don't use the paging control), multiple entries with a search limit
(wildcard request) or multiple entries with no limit (enumeration).
---
 src/providers/ldap/ldap_auth.c         |  2 +-
 src/providers/ldap/ldap_id.c           | 16 +++++++-------
 src/providers/ldap/sdap_async.h        | 12 +++++++---
 src/providers/ldap/sdap_async_enum.c   |  4 ++--
 src/providers/ldap/sdap_async_groups.c | 40 ++++++++++++++++++++++++----------
 src/providers/ldap/sdap_async_users.c  | 32 ++++++++++++++++++++-------
 6 files changed, 73 insertions(+), 33 deletions(-)

diff --git a/src/providers/ldap/ldap_auth.c b/src/providers/ldap/ldap_auth.c
index 81717942613b4a91ebab668ba2ecfe13caab38be..217e80fd07abc41f2594d19397783683d44600cd 100644
--- a/src/providers/ldap/ldap_auth.c
+++ b/src/providers/ldap/ldap_auth.c
@@ -418,7 +418,7 @@ static struct tevent_req *get_user_dn_send(TALLOC_CTX *memctx,
                                    sh, attrs, filter,
                                    dp_opt_get_int(opts->basic,
                                                   SDAP_SEARCH_TIMEOUT),
-                                   false);
+                                   SDAP_LOOKUP_SINGLE);
     if (!subreq) {
         ret = ENOMEM;
         goto done;
diff --git a/src/providers/ldap/ldap_id.c b/src/providers/ldap/ldap_id.c
index f3e829a1dab450bdaf1afc5d83cbff5e0aaeef1d..9635507b752a6870121d0efdf62a4d167f3f7d14 100644
--- a/src/providers/ldap/ldap_id.c
+++ b/src/providers/ldap/ldap_id.c
@@ -368,13 +368,13 @@ static void users_get_search(struct tevent_req *req)
     struct users_get_state *state = tevent_req_data(req,
                                                      struct users_get_state);
     struct tevent_req *subreq;
-    bool multiple_results;
+    enum sdap_entry_lookup_type lookup_type;
 
     if (state->filter_type == BE_FILTER_WILDCARD) {
-        multiple_results = true;
+        lookup_type = SDAP_LOOKUP_WILDCARD;
         state->req_start = time(NULL);
     } else {
-        multiple_results = false;
+        lookup_type = SDAP_LOOKUP_SINGLE;
     }
 
     subreq = sdap_get_users_send(state, state->ev,
@@ -385,7 +385,7 @@ static void users_get_search(struct tevent_req *req)
                                  state->attrs, state->filter,
                                  dp_opt_get_int(state->ctx->opts->basic,
                                                 SDAP_SEARCH_TIMEOUT),
-                                 multiple_results);
+                                 lookup_type);
     if (!subreq) {
         tevent_req_error(req, ENOMEM);
         return;
@@ -875,13 +875,13 @@ static void groups_get_search(struct tevent_req *req)
     struct groups_get_state *state = tevent_req_data(req,
                                                      struct groups_get_state);
     struct tevent_req *subreq;
-    bool multiple_results;
+    enum sdap_entry_lookup_type lookup_type;
 
     if (state->filter_type == BE_FILTER_WILDCARD) {
-        multiple_results = true;
+        lookup_type = SDAP_LOOKUP_WILDCARD;
         state->req_start = time(NULL);
     } else {
-        multiple_results = false;
+        lookup_type = SDAP_LOOKUP_SINGLE;
     }
 
     subreq = sdap_get_groups_send(state, state->ev,
@@ -891,7 +891,7 @@ static void groups_get_search(struct tevent_req *req)
                                   state->attrs, state->filter,
                                   dp_opt_get_int(state->ctx->opts->basic,
                                                  SDAP_SEARCH_TIMEOUT),
-                                  multiple_results,
+                                  lookup_type,
                                   state->no_members);
     if (!subreq) {
         tevent_req_error(req, ENOMEM);
diff --git a/src/providers/ldap/sdap_async.h b/src/providers/ldap/sdap_async.h
index b23dfc313905d01caedd1eace6bcb525481b9ebe..09bc0d65407253f93514b30877850cc38009c625 100644
--- a/src/providers/ldap/sdap_async.h
+++ b/src/providers/ldap/sdap_async.h
@@ -59,6 +59,12 @@ errno_t sdap_connect_host_recv(TALLOC_CTX *mem_ctx,
                                struct sdap_handle **_sh);
 
 /* Search users in LDAP, return them as attrs */
+enum sdap_entry_lookup_type {
+    SDAP_LOOKUP_SINGLE,         /* Direct single-user/group lookup */
+    SDAP_LOOKUP_WILDCARD,       /* Multiple entries with a limit */
+    SDAP_LOOKUP_ENUMERATE,      /* Fetch all entries from the server */
+};
+
 struct tevent_req *sdap_search_user_send(TALLOC_CTX *memctx,
                                          struct tevent_context *ev,
                                          struct sss_domain_info *dom,
@@ -68,7 +74,7 @@ struct tevent_req *sdap_search_user_send(TALLOC_CTX *memctx,
                                          const char **attrs,
                                          const char *filter,
                                          int timeout,
-                                         bool enumeration);
+                                         enum sdap_entry_lookup_type lookup_type);
 int sdap_search_user_recv(TALLOC_CTX *memctx, struct tevent_req *req,
                           char **higher_usn, struct sysdb_attrs ***users,
                           size_t *count);
@@ -84,7 +90,7 @@ struct tevent_req *sdap_get_users_send(TALLOC_CTX *memctx,
                                        const char **attrs,
                                        const char *filter,
                                        int timeout,
-                                       bool enumeration);
+                                       enum sdap_entry_lookup_type lookup_type);
 int sdap_get_users_recv(struct tevent_req *req,
                         TALLOC_CTX *mem_ctx, char **timestamp);
 
@@ -96,7 +102,7 @@ struct tevent_req *sdap_get_groups_send(TALLOC_CTX *memctx,
                                        const char **attrs,
                                        const char *filter,
                                        int timeout,
-                                       bool enumeration,
+                                       enum sdap_entry_lookup_type lookup_type,
                                        bool no_members);
 int sdap_get_groups_recv(struct tevent_req *req,
                          TALLOC_CTX *mem_ctx, char **timestamp);
diff --git a/src/providers/ldap/sdap_async_enum.c b/src/providers/ldap/sdap_async_enum.c
index 35afc55f809669a44aa2beda7d87dfe62d6ec10b..f22276c3ce6f839b765bbc1602fafb010cc37d89 100644
--- a/src/providers/ldap/sdap_async_enum.c
+++ b/src/providers/ldap/sdap_async_enum.c
@@ -635,7 +635,7 @@ static struct tevent_req *enum_users_send(TALLOC_CTX *memctx,
                                  state->attrs, state->filter,
                                  dp_opt_get_int(state->ctx->opts->basic,
                                                 SDAP_ENUM_SEARCH_TIMEOUT),
-                                 true);
+                                 SDAP_LOOKUP_ENUMERATE);
     if (!subreq) {
         ret = ENOMEM;
         goto fail;
@@ -811,7 +811,7 @@ static struct tevent_req *enum_groups_send(TALLOC_CTX *memctx,
                                   state->attrs, state->filter,
                                   dp_opt_get_int(state->ctx->opts->basic,
                                                  SDAP_ENUM_SEARCH_TIMEOUT),
-                                  true, false);
+                                  SDAP_LOOKUP_ENUMERATE, false);
     if (!subreq) {
         ret = ENOMEM;
         goto fail;
diff --git a/src/providers/ldap/sdap_async_groups.c b/src/providers/ldap/sdap_async_groups.c
index e785307e60d0df5be96a5b2de2c07baabaf1e371..ad0354df1fce9011c68cabb6049e7feee92a44c0 100644
--- a/src/providers/ldap/sdap_async_groups.c
+++ b/src/providers/ldap/sdap_async_groups.c
@@ -1721,7 +1721,7 @@ struct sdap_get_groups_state {
     const char *base_filter;
     char *filter;
     int timeout;
-    bool enumeration;
+    enum sdap_entry_lookup_type lookup_type;
     bool no_members;
 
     char *higher_usn;
@@ -1752,7 +1752,7 @@ struct tevent_req *sdap_get_groups_send(TALLOC_CTX *memctx,
                                        const char **attrs,
                                        const char *filter,
                                        int timeout,
-                                       bool enumeration,
+                                       enum sdap_entry_lookup_type lookup_type,
                                        bool no_members)
 {
     errno_t ret;
@@ -1775,7 +1775,7 @@ struct tevent_req *sdap_get_groups_send(TALLOC_CTX *memctx,
     state->groups =  NULL;
     state->count = 0;
     state->timeout = timeout;
-    state->enumeration = enumeration;
+    state->lookup_type = lookup_type;
     state->no_members = no_members;
     state->base_filter = filter;
     state->base_iter = 0;
@@ -1855,6 +1855,7 @@ static errno_t sdap_get_groups_next_base(struct tevent_req *req)
 {
     struct tevent_req *subreq;
     struct sdap_get_groups_state *state;
+    bool need_paging = false;
 
     state = tevent_req_data(req, struct sdap_get_groups_state);
 
@@ -1870,6 +1871,19 @@ static errno_t sdap_get_groups_next_base(struct tevent_req *req)
           "Searching for groups with base [%s]\n",
            state->search_bases[state->base_iter]->basedn);
 
+    switch (state->lookup_type) {
+    case SDAP_LOOKUP_SINGLE:
+        need_paging = false;
+        break;
+    /* Only requests that can return multiple entries should require
+     * the paging control
+     */
+    case SDAP_LOOKUP_WILDCARD:
+    case SDAP_LOOKUP_ENUMERATE:
+        need_paging = true;
+        break;
+    }
+
     subreq = sdap_get_and_parse_generic_send(
             state, state->ev, state->opts,
             state->ldap_sh != NULL ? state->ldap_sh : state->sh,
@@ -1878,7 +1892,7 @@ static errno_t sdap_get_groups_next_base(struct tevent_req *req)
             state->filter, state->attrs,
             state->opts->group_map, SDAP_OPTS_GROUP,
             0, NULL, NULL, 0, state->timeout,
-            state->enumeration); /* If we're enumerating, we need paging */
+            need_paging);
     if (!subreq) {
         return ENOMEM;
     }
@@ -1914,14 +1928,17 @@ static void sdap_get_groups_process(struct tevent_req *subreq)
     DEBUG(SSSDBG_TRACE_FUNC,
           "Search for groups, returned %zu results.\n", count);
 
-    if (!state->enumeration && count > 1) {
+    if (state->lookup_type == SDAP_LOOKUP_SINGLE && count > 1) {
         DEBUG(SSSDBG_MINOR_FAILURE,
               "Individual group search returned multiple results\n");
         tevent_req_error(req, EINVAL);
         return;
     }
 
-    if (state->enumeration || count == 0) {
+    if (state->lookup_type == SDAP_LOOKUP_WILDCARD || \
+            state->lookup_type == SDAP_LOOKUP_ENUMERATE || \
+        count == 0) {
+        /* No users found in this search or looking up multiple entries */
         next_base = true;
     }
 
@@ -2003,7 +2020,7 @@ static void sdap_get_groups_process(struct tevent_req *subreq)
      * LDAP_MATCHING_RULE_IN_CHAIN available in
      * AD 2008 and later
      */
-    if (!state->enumeration) {
+    if (state->lookup_type == SDAP_LOOKUP_SINGLE) {
         if ((state->opts->schema_type != SDAP_SCHEMA_RFC2307)
                 && (dp_opt_get_int(state->opts->basic, SDAP_NESTING_LEVEL) != 0)
                 && !dp_opt_get_bool(state->opts->basic, SDAP_AD_MATCHING_RULE_GROUPS)) {
@@ -2026,7 +2043,7 @@ static void sdap_get_groups_process(struct tevent_req *subreq)
     /* If we're using LDAP_MATCHING_RULE_IN_CHAIN, start a subreq to
      * retrieve the members so we can save them in a single step.
      */
-    if (!state->enumeration
+    if (state->lookup_type == SDAP_LOOKUP_SINGLE
             && (state->opts->schema_type != SDAP_SCHEMA_RFC2307)
             && state->opts->support_matching_rule
             && dp_opt_get_bool(state->opts->basic, SDAP_AD_MATCHING_RULE_GROUPS)) {
@@ -2050,7 +2067,8 @@ static void sdap_get_groups_process(struct tevent_req *subreq)
         return;
     }
 
-    if (state->enumeration
+    if ((state->lookup_type == SDAP_LOOKUP_ENUMERATE
+                || state->lookup_type == SDAP_LOOKUP_WILDCARD)
             && state->opts->schema_type != SDAP_SCHEMA_RFC2307
             && dp_opt_get_int(state->opts->basic, SDAP_NESTING_LEVEL) != 0) {
         DEBUG(SSSDBG_TRACE_ALL, "Saving groups without members first "
@@ -2069,7 +2087,7 @@ static void sdap_get_groups_process(struct tevent_req *subreq)
         subreq = sdap_process_group_send(state, state->ev, state->dom,
                                          state->sysdb, state->opts,
                                          state->sh, state->groups[i],
-                                         state->enumeration);
+                                         state->lookup_type == SDAP_LOOKUP_ENUMERATE);
 
         if (!subreq) {
             tevent_req_error(req, ENOMEM);
@@ -2116,7 +2134,7 @@ static void sdap_get_groups_done(struct tevent_req *subreq)
         ret = sdap_save_groups(state, state->sysdb, state->dom, state->opts,
                                state->groups, state->count,
                                !state->dom->ignore_group_members, NULL,
-                               !state->enumeration,
+                               state->lookup_type == SDAP_LOOKUP_SINGLE,
                                &state->higher_usn);
         if (ret) {
             DEBUG(SSSDBG_OP_FAILURE, "Failed to store groups.\n");
diff --git a/src/providers/ldap/sdap_async_users.c b/src/providers/ldap/sdap_async_users.c
index 216b49477bf21481265444c5c03df0aac7ee84e4..f66ae2604c867d4a5e8d223081ece9f1e474cf73 100644
--- a/src/providers/ldap/sdap_async_users.c
+++ b/src/providers/ldap/sdap_async_users.c
@@ -606,7 +606,7 @@ struct sdap_search_user_state {
     const char *base_filter;
     const char *filter;
     int timeout;
-    bool enumeration;
+    enum sdap_entry_lookup_type lookup_type;
 
     char *higher_usn;
     struct sysdb_attrs **users;
@@ -628,7 +628,7 @@ struct tevent_req *sdap_search_user_send(TALLOC_CTX *memctx,
                                          const char **attrs,
                                          const char *filter,
                                          int timeout,
-                                         bool enumeration)
+                                         enum sdap_entry_lookup_type lookup_type)
 {
     errno_t ret;
     struct tevent_req *req;
@@ -649,7 +649,7 @@ struct tevent_req *sdap_search_user_send(TALLOC_CTX *memctx,
     state->base_filter = filter;
     state->base_iter = 0;
     state->search_bases = search_bases;
-    state->enumeration = enumeration;
+    state->lookup_type = lookup_type;
 
     if (!state->search_bases) {
         DEBUG(SSSDBG_CRIT_FAILURE,
@@ -673,6 +673,7 @@ static errno_t sdap_search_user_next_base(struct tevent_req *req)
 {
     struct tevent_req *subreq;
     struct sdap_search_user_state *state;
+    bool need_paging = false;
 
     state = tevent_req_data(req, struct sdap_search_user_state);
 
@@ -688,6 +689,19 @@ static errno_t sdap_search_user_next_base(struct tevent_req *req)
           "Searching for users with base [%s]\n",
            state->search_bases[state->base_iter]->basedn);
 
+    switch (state->lookup_type) {
+    case SDAP_LOOKUP_SINGLE:
+        need_paging = false;
+        break;
+    /* Only requests that can return multiple entries should require
+     * the paging control
+     */
+    case SDAP_LOOKUP_WILDCARD:
+    case SDAP_LOOKUP_ENUMERATE:
+        need_paging = true;
+        break;
+    }
+
     subreq = sdap_get_and_parse_generic_send(
             state, state->ev, state->opts, state->sh,
             state->search_bases[state->base_iter]->basedn,
@@ -695,7 +709,7 @@ static errno_t sdap_search_user_next_base(struct tevent_req *req)
             state->filter, state->attrs,
             state->opts->user_map, state->opts->user_map_cnt,
             0, NULL, NULL, 0, state->timeout,
-            state->enumeration); /* If we're enumerating, we need paging */
+            need_paging);
     if (subreq == NULL) {
         return ENOMEM;
     }
@@ -726,8 +740,10 @@ static void sdap_search_user_process(struct tevent_req *subreq)
     DEBUG(SSSDBG_TRACE_FUNC,
           "Search for users, returned %zu results.\n", count);
 
-    if (state->enumeration || count == 0) {
-        /* No users found in this search or enumerating */
+    if (state->lookup_type == SDAP_LOOKUP_WILDCARD || \
+            state->lookup_type == SDAP_LOOKUP_ENUMERATE || \
+        count == 0) {
+        /* No users found in this search or looking up multiple entries */
         next_base = true;
     }
 
@@ -827,7 +843,7 @@ struct tevent_req *sdap_get_users_send(TALLOC_CTX *memctx,
                                        const char **attrs,
                                        const char *filter,
                                        int timeout,
-                                       bool enumeration)
+                                       enum sdap_entry_lookup_type lookup_type)
 {
     errno_t ret;
     struct tevent_req *req;
@@ -842,7 +858,7 @@ struct tevent_req *sdap_get_users_send(TALLOC_CTX *memctx,
     state->dom = dom;
 
     subreq = sdap_search_user_send(state, ev, dom, opts, search_bases,
-                                   sh, attrs, filter, timeout, enumeration);
+                                   sh, attrs, filter, timeout, lookup_type);
     if (subreq == NULL) {
         ret = ENOMEM;
         goto done;
-- 
2.4.3

-------------- next part --------------
>From bbb466a4621f1d0be7a6f9bf2f0c23ed9e46e4f8 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek at redhat.com>
Date: Wed, 17 Jun 2015 16:13:51 +0200
Subject: [PATCH 10/11] LDAP: Add the wildcard_limit option

Related:
    https://fedorahosted.org/sssd/ticket/2553

Adds a new wildcard_limit option that is set by default to 1000 (one
page). This option limits the number of entries that can by default be
returned by a wildcard search.
---
 src/config/SSSDConfig/__init__.py.in     |  1 +
 src/config/etc/sssd.api.d/sssd-ad.conf   |  1 +
 src/config/etc/sssd.api.d/sssd-ipa.conf  |  1 +
 src/config/etc/sssd.api.d/sssd-ldap.conf |  1 +
 src/man/sssd-ldap.5.xml                  | 17 +++++++++++++++++
 src/providers/ad/ad_opts.h               |  1 +
 src/providers/ipa/ipa_opts.h             |  1 +
 src/providers/ldap/ldap_opts.h           |  1 +
 src/providers/ldap/sdap.h                |  1 +
 src/providers/ldap/sdap_async_groups.c   |  8 +++++++-
 src/providers/ldap/sdap_async_users.c    |  8 +++++++-
 11 files changed, 39 insertions(+), 2 deletions(-)

diff --git a/src/config/SSSDConfig/__init__.py.in b/src/config/SSSDConfig/__init__.py.in
index af7f1bffbd910c0dee177eb64996233ac18fff19..2992c0d213ca10c63fa8e2ef9d3cdec2c04cd388 100644
--- a/src/config/SSSDConfig/__init__.py.in
+++ b/src/config/SSSDConfig/__init__.py.in
@@ -351,6 +351,7 @@ option_strings = {
     'ldap_min_id' : _('Set lower boundary for allowed IDs from the LDAP server'),
     'ldap_max_id' : _('Set upper boundary for allowed IDs from the LDAP server'),
     'ldap_pwdlockout_dn' : _('DN for ppolicy queries'),
+    'wildcard_limit' : _('How many maximum entries to fetch during a wildcard request'),
 
     # [provider/ldap/auth]
     'ldap_pwd_policy' : _('Policy to evaluate the password expiration'),
diff --git a/src/config/etc/sssd.api.d/sssd-ad.conf b/src/config/etc/sssd.api.d/sssd-ad.conf
index c46f3a7cb50db519d113e15f425c7f746d34ad81..024f1247b55f963c9d4f04b986fd686252af513f 100644
--- a/src/config/etc/sssd.api.d/sssd-ad.conf
+++ b/src/config/etc/sssd.api.d/sssd-ad.conf
@@ -56,6 +56,7 @@ ldap_deref_threshold = int, None, false
 ldap_connection_expire_timeout = int, None, false
 ldap_disable_paging = bool, None, false
 krb5_confd_path = str, None, false
+wildcard_limit = int, None, false
 
 [provider/ad/id]
 ldap_search_timeout = int, None, false
diff --git a/src/config/etc/sssd.api.d/sssd-ipa.conf b/src/config/etc/sssd.api.d/sssd-ipa.conf
index 6bae609fa9ff57e70c195b858eeea4eca680f62f..f75be6feb500399c653f0eeaa0e909601d8b4330 100644
--- a/src/config/etc/sssd.api.d/sssd-ipa.conf
+++ b/src/config/etc/sssd.api.d/sssd-ipa.conf
@@ -52,6 +52,7 @@ ldap_deref_threshold = int, None, false
 ldap_connection_expire_timeout = int, None, false
 ldap_disable_paging = bool, None, false
 krb5_confd_path = str, None, false
+ldap_pwdlockout_dn = str, None, false
 
 [provider/ipa/id]
 ldap_search_timeout = int, None, false
diff --git a/src/config/etc/sssd.api.d/sssd-ldap.conf b/src/config/etc/sssd.api.d/sssd-ldap.conf
index ba5f56f1942da552fc6ab8f82851714756683a8f..39ff530e1b32c5a3458864f50a5dd290b694db5f 100644
--- a/src/config/etc/sssd.api.d/sssd-ldap.conf
+++ b/src/config/etc/sssd.api.d/sssd-ldap.conf
@@ -37,6 +37,7 @@ ldap_sasl_minssf = int, None, false
 ldap_connection_expire_timeout = int, None, false
 ldap_disable_paging = bool, None, false
 ldap_disable_range_retrieval = bool, None, false
+ldap_pwdlockout_dn = str, None, false
 
 [provider/ldap/id]
 ldap_search_timeout = int, None, false
diff --git a/src/man/sssd-ldap.5.xml b/src/man/sssd-ldap.5.xml
index 1b7a2609ac4ebd0bccfd15a04d041e283bcf9cab..9d3d11b696f6f99d2d6d1d9a34d7d45910666669 100644
--- a/src/man/sssd-ldap.5.xml
+++ b/src/man/sssd-ldap.5.xml
@@ -2121,6 +2121,23 @@ ldap_access_filter = (employeeType=admin)
                     </listitem>
                 </varlistentry>
 
+                <varlistentry>
+                    <term>wildcart_limit (integer)</term>
+                    <listitem>
+                        <para>
+                            Specifies an upper limit on the number of entries
+                            that are downloaded during a wildcard lookup.
+                        </para>
+                        <para>
+                            At the moment, only the InfoPipe responder supports
+                            wildcard lookups.
+                        </para>
+                        <para>
+                            Default: 1000 (often the size of one page)
+                        </para>
+                    </listitem>
+                </varlistentry>
+
             </variablelist>
         </para>
     </refsect1>
diff --git a/src/providers/ad/ad_opts.h b/src/providers/ad/ad_opts.h
index 0f03d33839affe1381fd5eb72bfeba6e5b1dcc68..8eed9bde34b6be9277bec1cfd6d724d371180eda 100644
--- a/src/providers/ad/ad_opts.h
+++ b/src/providers/ad/ad_opts.h
@@ -146,6 +146,7 @@ struct dp_option ad_def_ldap_opts[] = {
     { "ldap_min_id", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER},
     { "ldap_max_id", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER},
     { "ldap_pwdlockout_dn", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "wildcard_limit", DP_OPT_NUMBER, { .number = 1000 }, NULL_NUMBER},
     DP_OPTION_TERMINATOR
 };
 
diff --git a/src/providers/ipa/ipa_opts.h b/src/providers/ipa/ipa_opts.h
index 34e9e167eb46f290d017b5af817571122b359b4f..efc89fb4e182380fa70793f6cf81dc87d8ebb682 100644
--- a/src/providers/ipa/ipa_opts.h
+++ b/src/providers/ipa/ipa_opts.h
@@ -157,6 +157,7 @@ struct dp_option ipa_def_ldap_opts[] = {
     { "ldap_min_id", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER},
     { "ldap_max_id", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER},
     { "ldap_pwdlockout_dn", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "wildcard_limit", DP_OPT_NUMBER, { .number = 1000 }, NULL_NUMBER},
     DP_OPTION_TERMINATOR
 };
 
diff --git a/src/providers/ldap/ldap_opts.h b/src/providers/ldap/ldap_opts.h
index f449ec7c309a5f664941cdefbb3c45a2fab10c99..0002f25e69c0abb8c5ccbf0a4af8471ab83934fc 100644
--- a/src/providers/ldap/ldap_opts.h
+++ b/src/providers/ldap/ldap_opts.h
@@ -122,6 +122,7 @@ struct dp_option default_basic_opts[] = {
     { "ldap_min_id", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER},
     { "ldap_max_id", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER},
     { "ldap_pwdlockout_dn", DP_OPT_STRING, NULL_STRING, NULL_STRING },
+    { "wildcard_limit", DP_OPT_NUMBER, { .number = 1000 }, NULL_NUMBER},
     DP_OPTION_TERMINATOR
 };
 
diff --git a/src/providers/ldap/sdap.h b/src/providers/ldap/sdap.h
index 6612ab200d7ee34be29512ba8df2744ab2a7ecd8..642902f5eff6f7e1841a1dbf68841688ebb676dd 100644
--- a/src/providers/ldap/sdap.h
+++ b/src/providers/ldap/sdap.h
@@ -231,6 +231,7 @@ enum sdap_basic_opt {
     SDAP_MIN_ID,
     SDAP_MAX_ID,
     SDAP_PWDLOCKOUT_DN,
+    SDAP_WILDCARD_LIMIT,
 
     SDAP_OPTS_BASIC /* opts counter */
 };
diff --git a/src/providers/ldap/sdap_async_groups.c b/src/providers/ldap/sdap_async_groups.c
index ad0354df1fce9011c68cabb6049e7feee92a44c0..525c6fa09553d8c0232ce2317751184f83632d86 100644
--- a/src/providers/ldap/sdap_async_groups.c
+++ b/src/providers/ldap/sdap_async_groups.c
@@ -1856,6 +1856,7 @@ static errno_t sdap_get_groups_next_base(struct tevent_req *req)
     struct tevent_req *subreq;
     struct sdap_get_groups_state *state;
     bool need_paging = false;
+    int sizelimit = 0;
 
     state = tevent_req_data(req, struct sdap_get_groups_state);
 
@@ -1873,13 +1874,18 @@ static errno_t sdap_get_groups_next_base(struct tevent_req *req)
 
     switch (state->lookup_type) {
     case SDAP_LOOKUP_SINGLE:
+        sizelimit = 1;
         need_paging = false;
         break;
     /* Only requests that can return multiple entries should require
      * the paging control
      */
     case SDAP_LOOKUP_WILDCARD:
+        sizelimit = dp_opt_get_int(state->opts->basic, SDAP_WILDCARD_LIMIT);
+        need_paging = true;
+        break;
     case SDAP_LOOKUP_ENUMERATE:
+        sizelimit = 0;  /* unlimited */
         need_paging = true;
         break;
     }
@@ -1891,7 +1897,7 @@ static errno_t sdap_get_groups_next_base(struct tevent_req *req)
             state->search_bases[state->base_iter]->scope,
             state->filter, state->attrs,
             state->opts->group_map, SDAP_OPTS_GROUP,
-            0, NULL, NULL, 0, state->timeout,
+            0, NULL, NULL, sizelimit, state->timeout,
             need_paging);
     if (!subreq) {
         return ENOMEM;
diff --git a/src/providers/ldap/sdap_async_users.c b/src/providers/ldap/sdap_async_users.c
index f66ae2604c867d4a5e8d223081ece9f1e474cf73..a864a8b2187de7972aa963b355856e97f7c692a9 100644
--- a/src/providers/ldap/sdap_async_users.c
+++ b/src/providers/ldap/sdap_async_users.c
@@ -674,6 +674,7 @@ static errno_t sdap_search_user_next_base(struct tevent_req *req)
     struct tevent_req *subreq;
     struct sdap_search_user_state *state;
     bool need_paging = false;
+    int sizelimit = 0;
 
     state = tevent_req_data(req, struct sdap_search_user_state);
 
@@ -691,13 +692,18 @@ static errno_t sdap_search_user_next_base(struct tevent_req *req)
 
     switch (state->lookup_type) {
     case SDAP_LOOKUP_SINGLE:
+        sizelimit = 1;
         need_paging = false;
         break;
     /* Only requests that can return multiple entries should require
      * the paging control
      */
     case SDAP_LOOKUP_WILDCARD:
+        sizelimit = dp_opt_get_int(state->opts->basic, SDAP_WILDCARD_LIMIT);
+        need_paging = true;
+        break;
     case SDAP_LOOKUP_ENUMERATE:
+        sizelimit = 0;  /* unlimited */
         need_paging = true;
         break;
     }
@@ -708,7 +714,7 @@ static errno_t sdap_search_user_next_base(struct tevent_req *req)
             state->search_bases[state->base_iter]->scope,
             state->filter, state->attrs,
             state->opts->user_map, state->opts->user_map_cnt,
-            0, NULL, NULL, 0, state->timeout,
+            0, NULL, NULL, sizelimit, state->timeout,
             need_paging);
     if (subreq == NULL) {
         return ENOMEM;
-- 
2.4.3

-------------- next part --------------
>From 8c496af03b1a57f92e03f2c02f80c57c84d05a9a Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek at redhat.com>
Date: Wed, 17 Jun 2015 13:39:43 +0200
Subject: [PATCH 11/11] IFP: Add wildcard requests

Resolves:
    https://fedorahosted.org/sssd/ticket/2553

Can be used as:

dbus-send --print-reply --system --dest=org.freedesktop.sssd.infopipe \
        /org/freedesktop/sssd/infopipe/Users \
        org.freedesktop.sssd.infopipe.Users.ListByName \
        string:r\* uint32:10

dbus-send --print-reply --system --dest=org.freedesktop.sssd.infopipe \
        /org/freedesktop/sssd/infopipe/Groups \
        org.freedesktop.sssd.infopipe.Groups.ListByName \
        string:r\* uint32:10

dbus-send --print-reply --system --dest=org.freedesktop.sssd.infopipe \
        /org/freedesktop/sssd/infopipe/Users \
        org.freedesktop.sssd.infopipe.Users.ListByDomainAndName \
        string:ipaldap string:r\* uint32:10

dbus-send --print-reply --system --dest=org.freedesktop.sssd.infopipe \
        /org/freedesktop/sssd/infopipe/Groups \
        org.freedesktop.sssd.infopipe.Groups.ListByDomainAndName \
        string:ipaldap string:r\* uint32:10

By default the wildcard_limit is unset, that is, the request will return
all cached entries that match.
---
 src/confdb/confdb.h             |   1 +
 src/man/sssd-ifp.5.xml          |  15 ++++
 src/responder/ifp/ifp_groups.c  | 175 ++++++++++++++++++++++++++++++++++++++
 src/responder/ifp/ifp_private.h |  21 +++++
 src/responder/ifp/ifp_users.c   | 184 ++++++++++++++++++++++++++++++++++++++++
 src/responder/ifp/ifpsrv.c      |  23 +++++
 src/responder/ifp/ifpsrv_util.c |  51 +++++++++++
 7 files changed, 470 insertions(+)

diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h
index 801a13fc2345e4b940963ad095bf01385c4bcd85..915f442766bf78fa019ee46f5589b10d7ea0cfee 100644
--- a/src/confdb/confdb.h
+++ b/src/confdb/confdb.h
@@ -140,6 +140,7 @@
 /* InfoPipe */
 #define CONFDB_IFP_CONF_ENTRY "config/ifp"
 #define CONFDB_IFP_USER_ATTR_LIST "user_attributes"
+#define CONFDB_IFP_WILDCARD_LIMIT "wildcard_limit"
 
 /* Domains */
 #define CONFDB_DOMAIN_PATH_TMPL "config/domain/%s"
diff --git a/src/man/sssd-ifp.5.xml b/src/man/sssd-ifp.5.xml
index 867c117edccc3c000f7d9e8456298b72ebcdf693..da247f89dd2d9d08e0b1591d4c89f52197b278df 100644
--- a/src/man/sssd-ifp.5.xml
+++ b/src/man/sssd-ifp.5.xml
@@ -131,6 +131,21 @@ user_attributes = +telephoneNumber, -loginShell
                         </para>
                     </listitem>
                 </varlistentry>
+
+                <varlistentry>
+                    <term>wildcart_limit (integer)</term>
+                    <listitem>
+                        <para>
+                            Specifies an upper limit on the number of entries
+                            that are downloaded during a wildcard lookup that
+                            overrides caller-supplied limit.
+                        </para>
+                        <para>
+                            Default: 0 (let the caller set an upper limit)
+                        </para>
+                    </listitem>
+                </varlistentry>
+
             </variablelist>
     </refsect1>
 
diff --git a/src/responder/ifp/ifp_groups.c b/src/responder/ifp/ifp_groups.c
index f1f86ce1ae29aaa906c2ebd56b81476ce18dfa1a..f1eb43195d02626104252e9de79c8036428e299e 100644
--- a/src/responder/ifp/ifp_groups.c
+++ b/src/responder/ifp/ifp_groups.c
@@ -80,6 +80,27 @@ done:
     return ret;
 }
 
+static int ifp_groups_list_copy(struct ifp_list_ctx *list_ctx,
+                                struct ldb_result *result)
+{
+    size_t copy_count, i;
+
+    copy_count = ifp_list_ctx_cap(list_ctx, result->count);
+
+    for (i = 0; i < copy_count; i++) {
+        list_ctx->paths[list_ctx->path_count + i] = \
+            ifp_groups_build_path_from_msg(list_ctx->paths,
+                                           list_ctx->dom,
+                                           result->msgs[i]);
+        if (list_ctx->paths[list_ctx->path_count + i] == NULL) {
+            return ENOMEM;
+        }
+    }
+
+    list_ctx->path_count += copy_count;
+    return EOK;
+}
+
 static void ifp_groups_find_by_name_done(struct tevent_req *req);
 
 int ifp_groups_find_by_name(struct sbus_request *sbus_req,
@@ -220,23 +241,177 @@ done:
     return;
 }
 
+static int ifp_groups_list_by_name_step(struct ifp_list_ctx *list_ctx);
+static void ifp_groups_list_by_name_done(struct tevent_req *req);
+static void ifp_groups_list_by_name_reply(struct ifp_list_ctx *list_ctx);
+
 int ifp_groups_list_by_name(struct sbus_request *sbus_req,
                             void *data,
                             const char *filter,
                             uint32_t limit)
 {
+    struct ifp_ctx *ctx;
+    struct ifp_list_ctx *list_ctx;
+
+    ctx = talloc_get_type(data, struct ifp_ctx);
+    if (ctx == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Invalid pointer!\n");
+        return ERR_INTERNAL;
+    }
+
+    list_ctx = ifp_list_ctx_new(sbus_req, ctx, filter, limit);
+    if (list_ctx == NULL) {
+        return ENOMEM;
+    }
+
+    return ifp_groups_list_by_name_step(list_ctx);
+}
+
+static int ifp_groups_list_by_name_step(struct ifp_list_ctx *list_ctx)
+{
+    struct tevent_req *req;
+
+    req = cache_req_group_by_filter_send(list_ctx,
+                                        list_ctx->ctx->rctx->ev,
+                                        list_ctx->ctx->rctx,
+                                        list_ctx->dom->name,
+                                        list_ctx->filter);
+    if (req == NULL) {
+        return ENOMEM;
+    }
+    tevent_req_set_callback(req,
+                            ifp_groups_list_by_name_done, list_ctx);
+
     return EOK;
 }
 
+static void ifp_groups_list_by_name_done(struct tevent_req *req)
+{
+    DBusError *error;
+    struct ifp_list_ctx *list_ctx;
+    struct sbus_request *sbus_req;
+    struct ldb_result *result;
+    struct sss_domain_info *domain;
+    errno_t ret;
+
+    list_ctx = tevent_req_callback_data(req, struct ifp_list_ctx);
+    sbus_req = list_ctx->sbus_req;
+
+    ret = cache_req_group_by_name_recv(sbus_req, req, &result, &domain, NULL);
+    talloc_zfree(req);
+    if (ret != EOK && ret != ENOENT) {
+        error = sbus_error_new(sbus_req, DBUS_ERROR_FAILED, "Failed to fetch "
+                               "groups by filter [%d]: %s\n", ret, sss_strerror(ret));
+        sbus_request_fail_and_finish(sbus_req, error);
+        return;
+    }
+
+    ret = ifp_groups_list_copy(list_ctx, result);
+    if (ret != EOK) {
+        error = sbus_error_new(sbus_req, SBUS_ERROR_INTERNAL,
+                               "Failed to copy domain result");
+        sbus_request_fail_and_finish(sbus_req, error);
+        return;
+    }
+
+    list_ctx->dom = get_next_domain(list_ctx->dom, true);
+    if (list_ctx->dom == NULL) {
+        return ifp_groups_list_by_name_reply(list_ctx);
+    }
+
+    ret = ifp_groups_list_by_name_step(list_ctx);
+    if (ret != EOK) {
+        error = sbus_error_new(sbus_req, SBUS_ERROR_INTERNAL,
+                               "Failed to start next-domain search");
+        sbus_request_fail_and_finish(sbus_req, error);
+        return;
+    }
+}
+
+static void ifp_groups_list_by_name_reply(struct ifp_list_ctx *list_ctx)
+{
+    iface_ifp_groups_ListByDomainAndName_finish(list_ctx->sbus_req,
+                                               list_ctx->paths,
+                                               list_ctx->path_count);
+}
+
+static void ifp_groups_list_by_domain_and_name_done(struct tevent_req *req);
+
 int ifp_groups_list_by_domain_and_name(struct sbus_request *sbus_req,
                                        void *data,
                                        const char *domain,
                                        const char *filter,
                                        uint32_t limit)
 {
+    struct tevent_req *req;
+    struct ifp_ctx *ctx;
+    struct ifp_list_ctx *list_ctx;
+
+    ctx = talloc_get_type(data, struct ifp_ctx);
+    if (ctx == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Invalid pointer!\n");
+        return ERR_INTERNAL;
+    }
+
+    list_ctx = ifp_list_ctx_new(sbus_req, ctx, filter, limit);
+    if (list_ctx == NULL) {
+        return ENOMEM;
+    }
+
+    req = cache_req_group_by_filter_send(list_ctx, ctx->rctx->ev, ctx->rctx,
+                                        domain, filter);
+    if (req == NULL) {
+        return ENOMEM;
+    }
+    tevent_req_set_callback(req,
+                            ifp_groups_list_by_domain_and_name_done, list_ctx);
+
     return EOK;
 }
 
+static void ifp_groups_list_by_domain_and_name_done(struct tevent_req *req)
+{
+    DBusError *error;
+    struct ifp_list_ctx *list_ctx;
+    struct sbus_request *sbus_req;
+    struct ldb_result *result;
+    struct sss_domain_info *domain;
+    errno_t ret;
+
+    list_ctx = tevent_req_callback_data(req, struct ifp_list_ctx);
+    sbus_req = list_ctx->sbus_req;
+
+    ret = cache_req_user_by_name_recv(sbus_req, req, &result, &domain, NULL);
+    talloc_zfree(req);
+    if (ret == ENOENT) {
+        error = sbus_error_new(sbus_req, SBUS_ERROR_NOT_FOUND,
+                               "User not found by filter");
+        goto done;
+    } else if (ret != EOK) {
+        error = sbus_error_new(sbus_req, DBUS_ERROR_FAILED, "Failed to fetch "
+                               "groups by filter [%d]: %s\n", ret, sss_strerror(ret));
+        goto done;
+    }
+
+    ret = ifp_groups_list_copy(list_ctx, result);
+    if (ret != EOK) {
+        error = sbus_error_new(sbus_req, SBUS_ERROR_INTERNAL,
+                               "Failed to copy domain result");
+        goto done;
+    }
+
+done:
+    if (ret != EOK) {
+        sbus_request_fail_and_finish(sbus_req, error);
+        return;
+    }
+
+    iface_ifp_groups_ListByDomainAndName_finish(sbus_req,
+                                                list_ctx->paths,
+                                                list_ctx->path_count);
+    return;
+}
+
 static errno_t
 ifp_groups_group_get(struct sbus_request *sbus_req,
                      void *data,
diff --git a/src/responder/ifp/ifp_private.h b/src/responder/ifp/ifp_private.h
index 94c9a4b85e2db7fd100dd7ea0b664081d6ba322e..38253719694f85cbe55ff322b4616194b1260b44 100644
--- a/src/responder/ifp/ifp_private.h
+++ b/src/responder/ifp/ifp_private.h
@@ -44,6 +44,7 @@ struct ifp_ctx {
 
     struct sysbus_ctx *sysbus;
     const char **user_whitelist;
+    uint32_t wildcard_limit;
 };
 
 errno_t ifp_register_sbus_interface(struct sbus_connection *conn,
@@ -82,4 +83,24 @@ ifp_get_user_extra_attributes(TALLOC_CTX *mem_ctx, struct ifp_ctx *ifp_ctx);
 bool ifp_attr_allowed(const char *whitelist[], const char *attr);
 bool ifp_is_user_attr_allowed(struct ifp_ctx *ifp_ctx, const char *attr);
 
+/* Used for list calls */
+struct ifp_list_ctx {
+    struct sbus_request *sbus_req;
+    const char *filter;
+    uint32_t limit;
+
+    struct sss_domain_info *dom;
+    struct ifp_ctx *ctx;
+
+    const char **paths;
+    size_t path_count;
+};
+
+struct ifp_list_ctx *ifp_list_ctx_new(struct sbus_request *sbus_req,
+                                      struct ifp_ctx *ctx,
+                                      const char *filter,
+                                      uint32_t limit);
+
+size_t ifp_list_ctx_cap(struct ifp_list_ctx *list_ctx, size_t entries);
+
 #endif /* _IFPSRV_PRIVATE_H_ */
diff --git a/src/responder/ifp/ifp_users.c b/src/responder/ifp/ifp_users.c
index 26c1aa696e5fbc3856ff45b0eb2c88d6bfbd2340..d3bd3db36e42918aebdba228b427734a10c7da11 100644
--- a/src/responder/ifp/ifp_users.c
+++ b/src/responder/ifp/ifp_users.c
@@ -221,23 +221,207 @@ done:
     return;
 }
 
+static int ifp_users_list_copy(struct ifp_list_ctx *list_ctx,
+                               struct ldb_result *result)
+{
+    size_t copy_count, i;
+
+    copy_count = ifp_list_ctx_cap(list_ctx, result->count);
+
+    for (i = 0; i < copy_count; i++) {
+        list_ctx->paths[list_ctx->path_count + i] = \
+                             ifp_users_build_path_from_msg(list_ctx->paths,
+                                                           list_ctx->dom,
+                                                           result->msgs[i]);
+        if (list_ctx->paths[list_ctx->path_count + i] == NULL) {
+            return ENOMEM;
+        }
+    }
+
+    list_ctx->path_count += copy_count;
+    return EOK;
+}
+
+static int ifp_users_list_by_name_step(struct ifp_list_ctx *list_ctx);
+static void ifp_users_list_by_name_done(struct tevent_req *req);
+static void ifp_users_list_by_name_reply(struct ifp_list_ctx *list_ctx);
+
 int ifp_users_list_by_name(struct sbus_request *sbus_req,
                            void *data,
                            const char *filter,
                            uint32_t limit)
 {
+    struct ifp_ctx *ctx;
+    struct ifp_list_ctx *list_ctx;
+
+    ctx = talloc_get_type(data, struct ifp_ctx);
+    if (ctx == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Invalid pointer!\n");
+        return ERR_INTERNAL;
+    }
+
+    list_ctx = ifp_list_ctx_new(sbus_req, ctx, filter, limit);
+    if (list_ctx == NULL) {
+        return ENOMEM;
+    }
+
+    return ifp_users_list_by_name_step(list_ctx);
+}
+
+static int ifp_users_list_by_name_step(struct ifp_list_ctx *list_ctx)
+{
+    struct tevent_req *req;
+
+    req = cache_req_user_by_filter_send(list_ctx,
+                                        list_ctx->ctx->rctx->ev,
+                                        list_ctx->ctx->rctx,
+                                        list_ctx->dom->name,
+                                        list_ctx->filter);
+    if (req == NULL) {
+        return ENOMEM;
+    }
+    tevent_req_set_callback(req,
+                            ifp_users_list_by_name_done, list_ctx);
+
     return EOK;
 }
 
+static void ifp_users_list_by_name_done(struct tevent_req *req)
+{
+    DBusError *error;
+    struct ifp_list_ctx *list_ctx;
+    struct sbus_request *sbus_req;
+    struct ldb_result *result;
+    struct sss_domain_info *domain;
+    errno_t ret;
+
+    list_ctx = tevent_req_callback_data(req, struct ifp_list_ctx);
+    sbus_req = list_ctx->sbus_req;
+
+    ret = cache_req_user_by_name_recv(sbus_req, req, &result, &domain, NULL);
+    talloc_zfree(req);
+    if (ret != EOK && ret != ENOENT) {
+        error = sbus_error_new(sbus_req, DBUS_ERROR_FAILED, "Failed to fetch "
+                               "users by filter [%d]: %s\n", ret, sss_strerror(ret));
+        sbus_request_fail_and_finish(sbus_req, error);
+        return;
+    }
+
+    ret = ifp_users_list_copy(list_ctx, result);
+    if (ret != EOK) {
+        error = sbus_error_new(sbus_req, SBUS_ERROR_INTERNAL,
+                               "Failed to copy domain result");
+        sbus_request_fail_and_finish(sbus_req, error);
+        return;
+    }
+
+    list_ctx->dom = get_next_domain(list_ctx->dom, true);
+    if (list_ctx->dom == NULL) {
+        return ifp_users_list_by_name_reply(list_ctx);
+    }
+
+    ret = ifp_users_list_by_name_step(list_ctx);
+    if (ret != EOK) {
+        error = sbus_error_new(sbus_req, SBUS_ERROR_INTERNAL,
+                               "Failed to start next-domain search");
+        sbus_request_fail_and_finish(sbus_req, error);
+        return;
+    }
+}
+
+static void ifp_users_list_by_name_reply(struct ifp_list_ctx *list_ctx)
+{
+    iface_ifp_users_ListByName_finish(list_ctx->sbus_req,
+                                      list_ctx->paths,
+                                      list_ctx->path_count);
+}
+
+static void ifp_users_list_by_domain_and_name_done(struct tevent_req *req);
+
 int ifp_users_list_by_domain_and_name(struct sbus_request *sbus_req,
                                       void *data,
                                       const char *domain,
                                       const char *filter,
                                       uint32_t limit)
 {
+    struct tevent_req *req;
+    struct ifp_ctx *ctx;
+    struct ifp_list_ctx *list_ctx;
+
+    ctx = talloc_get_type(data, struct ifp_ctx);
+    if (ctx == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Invalid pointer!\n");
+        return ERR_INTERNAL;
+    }
+
+    list_ctx = ifp_list_ctx_new(sbus_req, ctx, filter, limit);
+    if (list_ctx == NULL) {
+        return ENOMEM;
+    }
+
+    req = cache_req_user_by_filter_send(list_ctx, ctx->rctx->ev, ctx->rctx,
+                                        domain, filter);
+    if (req == NULL) {
+        return ENOMEM;
+    }
+    tevent_req_set_callback(req,
+                            ifp_users_list_by_domain_and_name_done, list_ctx);
+
     return EOK;
 }
 
+static void ifp_users_list_by_domain_and_name_done(struct tevent_req *req)
+{
+    DBusError *error;
+    struct ifp_list_ctx *list_ctx;
+    struct sbus_request *sbus_req;
+    struct ldb_result *result;
+    struct sss_domain_info *domain;
+    errno_t ret;
+    size_t copy_count, i;
+
+    list_ctx = tevent_req_callback_data(req, struct ifp_list_ctx);
+    sbus_req = list_ctx->sbus_req;
+
+    ret = cache_req_user_by_name_recv(sbus_req, req, &result, &domain, NULL);
+    talloc_zfree(req);
+    if (ret == ENOENT) {
+        error = sbus_error_new(sbus_req, SBUS_ERROR_NOT_FOUND,
+                               "User not found by filter");
+        goto done;
+    } else if (ret != EOK) {
+        error = sbus_error_new(sbus_req, DBUS_ERROR_FAILED, "Failed to fetch "
+                               "users by filter [%d]: %s\n", ret, sss_strerror(ret));
+        goto done;
+    }
+
+    copy_count = ifp_list_ctx_cap(list_ctx, result->count);
+
+    for (i = 0; i < copy_count; i++) {
+        list_ctx->paths[i] = ifp_users_build_path_from_msg(list_ctx->paths,
+                                                           list_ctx->dom,
+                                                           result->msgs[i]);
+        if (list_ctx->paths[i] == NULL) {
+            error = sbus_error_new(sbus_req, SBUS_ERROR_INTERNAL,
+                                   "Failed to compose object path");
+            goto done;
+        }
+    }
+
+    list_ctx->path_count += copy_count;
+
+done:
+    if (ret != EOK) {
+        sbus_request_fail_and_finish(sbus_req, error);
+        return;
+    }
+
+    iface_ifp_users_ListByDomainAndName_finish(sbus_req,
+                                               list_ctx->paths,
+                                               list_ctx->path_count);
+    return;
+}
+
 static errno_t
 ifp_users_user_get(struct sbus_request *sbus_req,
                    struct ifp_ctx *ifp_ctx,
diff --git a/src/responder/ifp/ifpsrv.c b/src/responder/ifp/ifpsrv.c
index f804ea1978b6b32590c67fa34e00f94008b7b423..830e31f8f79f550b69ab622dd8c673fdb25378a7 100644
--- a/src/responder/ifp/ifpsrv.c
+++ b/src/responder/ifp/ifpsrv.c
@@ -34,6 +34,7 @@
 #include <dbus/dbus.h>
 
 #include "util/util.h"
+#include "util/strtonum.h"
 #include "sbus/sssd_dbus.h"
 #include "monitor/monitor_interfaces.h"
 #include "confdb/confdb.h"
@@ -226,6 +227,7 @@ int ifp_process_init(TALLOC_CTX *mem_ctx,
     int max_retries;
     char *uid_str;
     char *attr_list_str;
+    char *wildcard_limit_str;
 
     ifp_cmds = get_ifp_cmds();
     ret = sss_process_init(mem_ctx, ev, cdb,
@@ -319,6 +321,27 @@ int ifp_process_init(TALLOC_CTX *mem_ctx,
         goto fail;
     }
 
+    /* A bit convoluted way until we have a confdb_get_uint32 */
+    ret = confdb_get_string(ifp_ctx->rctx->cdb,
+                            ifp_ctx->rctx,
+                            CONFDB_IFP_CONF_ENTRY,
+                            CONFDB_IFP_WILDCARD_LIMIT,
+                            NULL, /* no limit by default */
+                            &wildcard_limit_str);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_FATAL_FAILURE,
+              "Failed to retrieve limit for a wildcard search\n");
+        goto fail;
+    }
+
+    if (wildcard_limit_str) {
+        ifp_ctx->wildcard_limit = strtouint32(wildcard_limit_str, NULL, 10);
+        if (errno != 0) {
+            ret = errno;
+            goto fail;
+        }
+    }
+
     for (iter = ifp_ctx->rctx->be_conns; iter; iter = iter->next) {
         sbus_reconnect_init(iter->conn, max_retries,
                             ifp_dp_reconnect_init, iter);
diff --git a/src/responder/ifp/ifpsrv_util.c b/src/responder/ifp/ifpsrv_util.c
index 674165ee4901115c9e17458a75fdb3536b6468c2..4a723a0c9f9eaa50c1bb3ee6b27a4fcf7bbdaae7 100644
--- a/src/responder/ifp/ifpsrv_util.c
+++ b/src/responder/ifp/ifpsrv_util.c
@@ -21,6 +21,8 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+#include <sys/param.h>
+
 #include "db/sysdb.h"
 #include "responder/ifp/ifp_private.h"
 
@@ -269,3 +271,52 @@ ifp_is_user_attr_allowed(struct ifp_ctx *ifp_ctx, const char *attr)
 {
     return ifp_attr_allowed(ifp_ctx->user_whitelist, attr);
 }
+
+static uint32_t ifp_list_limit(struct ifp_ctx *ctx, uint32_t limit)
+{
+    if (ctx->wildcard_limit) {
+        return MIN(ctx->wildcard_limit, limit);
+    } else {
+        return limit;
+    }
+}
+
+struct ifp_list_ctx *ifp_list_ctx_new(struct sbus_request *sbus_req,
+                                      struct ifp_ctx *ctx,
+                                      const char *filter,
+                                      uint32_t limit)
+{
+    struct ifp_list_ctx *list_ctx;
+
+    list_ctx = talloc_zero(sbus_req, struct ifp_list_ctx);
+    if (list_ctx == NULL) {
+        return NULL;
+    }
+
+    list_ctx->sbus_req = sbus_req;
+    list_ctx->limit = ifp_list_limit(ctx, limit);
+    list_ctx->ctx = ctx;
+    list_ctx->dom = ctx->rctx->domains;
+    list_ctx->filter = filter;
+    list_ctx->paths = talloc_zero_array(list_ctx, const char *, limit);
+    if (list_ctx->paths == NULL) {
+        talloc_free(list_ctx);
+        return NULL;
+    }
+
+    return list_ctx;
+}
+
+size_t ifp_list_ctx_cap(struct ifp_list_ctx *list_ctx, size_t entries)
+{
+    size_t capacity = list_ctx->limit - list_ctx->path_count;
+
+    if (capacity < entries) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              "IFP list request has limit of %"PRIu32" entries but back end "
+              "returned %zu entries\n", list_ctx->limit, entries);
+        return capacity;
+    } else {
+        return entries;
+    }
+}
-- 
2.4.3



More information about the sssd-devel mailing list