[SSSD] [PATCHES] Wildcard lookups for InfoPipe

Jakub Hrozek jhrozek at redhat.com
Mon Jul 13 16:25:13 UTC 2015


On Fri, Jul 10, 2015 at 01:30:07PM +0200, Pavel Březina wrote:
> On 07/09/2015 12:17 PM, Jakub Hrozek wrote:
> >>It may be also better
> >>to move them into the sysdb module?
> >
> >I would do that if we anticipate more listing intefaces using this
> >kind of filter.
> 
> I disagree since in my opinion it belongs into sysdb, but I will not insist.
> 
> >>If I read the code correctly, you always return only records that were
> >>updated. It this sufficient? I think we need to return all records that
> >>match the provided filter.
> >
> >Please check my logic here, but I think the way it's coded now is
> >safer. On the back end side, we can't reasonably detect if an entry has
> >been removed if we only use the wildcard lookup. So the back end can't
> >reasonably decide which entries have been removed from the server.
> >
> >The reason is the limit. There might be more entries on the server side
> >that those that match the filter, so what the back end sees is just a
> >subset. And the entries outside the limit can be cached by direct
> >lookups, so we simply shouldn't touch them..
> >
> >What the code does now is only returns entries updated by the wildcard
> >lookup. The entries that were not updated are not returned to the
> >fronted -- they just sit in the cache until some application requests
> >them with a direct lookup. The direct lookup can detect removal of the
> >object on the server side and also remove the cached object.
> >
> >Does that make sense?
> 
> Yes, thank you for explanation. I think the logic is sound.
> 
> >>>    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 {
> >>
> >>I would like to avoid this type of code whenever possible. Can we replace it
> >>with a macro e.g. always_check_cache(state->input) or function?
> >
> >OK, done. Please feel free to suggest a better function name.
> >
> >>
> >>Is it a valid operation to use a filter that needs to be parse into name and
> >>domain (user*@domain)?
> >
> >I don't think so, we have a specialized function for that.

ListByDomainAndName

> 
> What function?
> 
> *Patch #01 tests: Move N_ELEMENTS definition to tests/common.h*
> ACK
> 
> *Patch #02 SYSDB: Add functions to look up multiple entries including name
> and custom filter*
> 
> >+static char *enum_filter(TALLOC_CTX *mem_ctx,
> >+                         const char *base_filter,
> >+                         const char *name_filter,
> >+                         const char *addtl_filter)
> 
> You are leaking memory here if any of the allocation fails. I know it will
> be freed in the caller but it is not a good practice.

As discussed on IRC, I added a context.

> 
> *Patch #03 DP: Add DP_WILDCARD and
> SSS_DP_WILDCARD_USER/SSS_DP_WILDCARD_GROUP*
> 
> >+        } 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);
> >+            }
> 
> Do you think it is safe to use the same prefix for both users and groups
> look up?

yes, because the target is defined with be_type.

> 
> *Patch #04 cache_req: Extend cache_req with wildcard lookups*
> 
> >    case CACHE_REQ_USER_BY_FILTER:
> >    case CACHE_REQ_GROUP_BY_FILTER:
> >        /* Nothing to do, adding a wildcard request to ncache doesn't
> >         * make sense */
> 
> >/* Return true if the request can be cached or false if the cache_req
> > * code needs to contact the DP every time
> > */
> 
> Can you finish the sentence with dot so it is consistent with the rest of
> the code?

Done.

> 
> >static bool cache_req_cachable(struct cache_req_input *input)
> 
> How about cache_req_avoid_cache?

OK, renamed.

> 
> *Patch #05 UTIL: Add sss_filter_sanitize_ex*
> 
> >+    const char has_all_allow_asterisk_expected[] = "\\5c\\28user\\29*name";
> 
> Can you add comment with unescaped characters so it is human-readable?

Done.

> 
> *Patch #06 LDAP: Fetch users and groups using wildcards*
> 
> >@@ -953,6 +991,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;
> 
> Copy and paste error... you meant groups here.

Yes, thanks. Fixed.

> 
> *Patch #07 LDAP: Add sdap_get_and_parse_generic_send*
> ACK
> 
> *Patch #08 LDAP: Use sdap_get_and_parse_generic_/_recv*
> ACK
> 
> *Patch #09 LDAP: Add sdap_lookup_type enum*
> ACK
> 
> *Patch #10 LDAP: Add the wildcard_limit option*
> 
> >+ldap_pwdlockout_dn = str, None, false
> 
> This probably belongs to separate patch...

Yeah, this was wrong.

> 
> *Patch #11 IFP: Add wildcard requests*
> 
> >size_t ifp_list_ctx_cap(struct ifp_list_ctx *list_ctx, size_t entries)
> 
> ifp_list_ctx_remaining_capacity? I had no idea what the function does before
> I read its code.

OK, renamed.

Thanks for the review, new patches are attached.
-------------- next part --------------
>From 0758b5f22664ec45557cdcf62bd87a8ba2cb5e1c 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 0b351f5d647a8f72bdbc399c6fe02579d4b4e1be..1c6de2c3d6eb924ba7306bd350f8546d61f30751 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 bef6bbd84eeb8ea27477987325be01a0c6fee19a 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               | 252 ++++++++++++------
 src/tests/cmocka/test_sysdb_views.c | 494 ++++++++++++++++++++++++++++++++++++
 3 files changed, 691 insertions(+), 79 deletions(-)

diff --git a/src/db/sysdb.h b/src/db/sysdb.h
index 48dd26dd294333b265b69b28cd3b5d37f1293b43..0f745ccb1a646d77ba4ad3d714d5f4dce0a51211 100644
--- a/src/db/sysdb.h
+++ b/src/db/sysdb.h
@@ -601,10 +601,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,
@@ -619,10 +631,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..dd9c8ff13e7ee3ca2e134094ae7ff242ad74f4ba 100644
--- a/src/db/sysdb_search.c
+++ b/src/db/sysdb_search.c
@@ -255,44 +255,104 @@ 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;
+    TALLOC_CTX *tmp_ctx = NULL;
+
+    tmp_ctx = talloc_new(NULL);
+    if (tmp_ctx == NULL) {
+        return NULL;
+    }
+
+    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, ")");
+        }
+    }
+
+    if (filter) {
+        talloc_steal(mem_ctx, filter);
+    }
+
+    talloc_free(tmp_ctx);
+    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 +365,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 +391,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 +729,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 +812,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 +846,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 4143f5a8d6c9b26834d99eb3a7b7aea5543e6d05 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 13a700308e9c9d1a80c0310dbb1c17de33607498..510c63ce41c99314ec8fcf11fffb2e66082e8951 100644
--- a/src/providers/data_provider.h
+++ b/src/providers/data_provider.h
@@ -129,6 +129,7 @@
 #define BE_FILTER_SECID 4
 #define BE_FILTER_UUID 5
 #define BE_FILTER_CERT 6
+#define BE_FILTER_WILDCARD 7
 
 #define BE_REQ_USER          0x0001
 #define BE_REQ_GROUP         0x0002
@@ -153,6 +154,9 @@
 #define DP_SEC_ID_LEN (sizeof(DP_SEC_ID) - 1)
 #define DP_CERT_LEN (sizeof(DP_CERT) - 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 f5bdfb676011975defa4c5a734d420c8694f3bdd..d147630248f0a24f5a632760b55b9284a6928e40 100644
--- a/src/providers/data_provider_be.c
+++ b/src/providers/data_provider_be.c
@@ -1204,6 +1204,11 @@ static int be_get_account_info(struct sbus_request *dbus_req, void *user_data)
             ret = split_name_extended(req, &filter[DP_CERT_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 bd0250d52aae1efdac3bc0847f692542c6cfb6d2..4d927cfe321bf3ad240b7c175568081ea73ab652 100644
--- a/src/responder/common/responder.h
+++ b/src/responder/common/responder.h
@@ -282,7 +282,9 @@ enum sss_dp_acct_type {
     SSS_DP_SERVICES,
     SSS_DP_SECID,
     SSS_DP_USER_AND_GROUP,
-    SSS_DP_CERT
+    SSS_DP_CERT,
+    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 f752c94c37fd0efa0d072ef8c567bd875a08e6f7..f7f8df04e80439d2d02df3e4d2aa4f3576997f36 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:
@@ -574,6 +576,15 @@ sss_dp_get_account_msg(void *pvt)
                 filter = talloc_asprintf(info, "%s=%s", DP_CERT,
                                                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 f0c86a2e5ba10486def8e73f19bda4dca78fe407 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  | 156 ++++++++++-
 src/responder/common/responder_cache_req.h  |  24 +-
 src/tests/cmocka/test_responder_cache_req.c | 414 +++++++++++++++++++++++++++-
 3 files changed, 579 insertions(+), 15 deletions(-)

diff --git a/src/responder/common/responder_cache_req.c b/src/responder/common/responder_cache_req.c
index dd81abadf71c5e10e7bc2ea2490429a49bdc0270..e7099f171d7ef39af7b146a524dadc38a9165e22 100644
--- a/src/responder/common/responder_cache_req.c
+++ b/src/responder/common/responder_cache_req.c
@@ -28,6 +28,44 @@
 #include "responder/common/responder_cache_req.h"
 #include "providers/data_provider.h"
 
+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;
+}
+
 struct cache_req_input {
     enum cache_req_type type;
 
@@ -51,6 +89,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 *
@@ -68,11 +108,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");
@@ -121,9 +164,18 @@ cache_req_input_create(TALLOC_CTX *mem_ctx,
     case CACHE_REQ_INITGROUPS:
         input->dp_type = SSS_DP_INITGROUPS;
         break;
+
     case CACHE_REQ_USER_BY_CERT:
         input->dp_type = SSS_DP_CERT;
         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;
@@ -157,7 +209,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);
@@ -171,6 +223,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);
@@ -185,8 +239,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;
         }
@@ -194,16 +248,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;
         }
@@ -211,10 +265,10 @@ cache_req_input_set_domain(struct cache_req_input *input,
     case CACHE_REQ_USER_BY_CERT:
         /* certificates might be quite long, only use the last 10 charcters
          * for logging */
-        fqn = talloc_asprintf(tmp_ctx, "CERT:%s@%s",
-                              get_last_x_chars(input->cert, 10),
-                              domain->name);
-        if (fqn == NULL) {
+        debug_fqn = talloc_asprintf(tmp_ctx, "CERT:%s@%s",
+                                    get_last_x_chars(input->cert, 10),
+                                    domain->name);
+        if (debug_fqn == NULL) {
             ret = ENOMEM;
             goto done;
         }
@@ -223,7 +277,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;
 
@@ -257,6 +311,10 @@ static errno_t cache_req_check_ncache(struct cache_req_input *input,
     case CACHE_REQ_USER_BY_CERT:
         ret = sss_ncache_check_cert(ncache, neg_timeout, input->cert);
         break;
+    case CACHE_REQ_USER_BY_FILTER:
+    case CACHE_REQ_GROUP_BY_FILTER:
+        ret = EOK;
+        break;
     }
 
     if (ret == EEXIST) {
@@ -282,6 +340,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 ncache doesn't
+         * make sense */
     case CACHE_REQ_USER_BY_ID:
     case CACHE_REQ_GROUP_BY_ID:
     case CACHE_REQ_USER_BY_CERT:
@@ -308,6 +370,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 ncache doesn't
+         * make sense */
     case CACHE_REQ_USER_BY_NAME:
     case CACHE_REQ_GROUP_BY_NAME:
     case CACHE_REQ_INITGROUPS:
@@ -377,6 +443,18 @@ static errno_t cache_req_get_object(TALLOC_CTX *mem_ctx,
         ret = sysdb_search_user_by_cert(mem_ctx, input->domain,
                                         input->cert, &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) {
@@ -397,6 +475,19 @@ done:
     return ret;
 }
 
+/* Return true if the request bypasses cache or false if the cache_req
+ * code can leverage sysdb for this request.
+ */
+static bool cache_req_bypass_cache(struct cache_req_input *input)
+{
+    if (input->type == CACHE_REQ_USER_BY_FILTER ||
+            input->type == CACHE_REQ_GROUP_BY_FILTER) {
+        return true;
+    }
+
+    return false;
+}
+
 struct cache_req_cache_state {
     /* input data */
     struct tevent_context *ev;
@@ -504,7 +595,8 @@ 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 ||
+            cache_req_bypass_cache(state->input) == true) {
         ret = ENOENT;
     } else {
         if (state->input->type == CACHE_REQ_INITGROUPS) {
@@ -1059,3 +1151,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, NULL);
+    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, NULL);
+    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 84a9dde7d9df066e44b1352e0a4557f02d08cc15..9e3f88a1427f3dcbde9f81df2ec647821b7aa931 100644
--- a/src/responder/common/responder_cache_req.h
+++ b/src/responder/common/responder_cache_req.h
@@ -33,7 +33,9 @@ enum cache_req_type {
     CACHE_REQ_GROUP_BY_NAME,
     CACHE_REQ_GROUP_BY_ID,
     CACHE_REQ_INITGROUPS,
-    CACHE_REQ_USER_BY_CERT
+    CACHE_REQ_USER_BY_CERT,
+    CACHE_REQ_USER_BY_FILTER,
+    CACHE_REQ_GROUP_BY_FILTER,
 };
 
 struct cache_req_input;
@@ -143,4 +145,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 d063d07e70b352c133d09b35993e6fad93da587e 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 |  9 +++++++++
 src/util/util.c        | 28 +++++++++++++++++++++++++---
 src/util/util.h        |  5 +++++
 3 files changed, 39 insertions(+), 3 deletions(-)

diff --git a/src/tests/util-tests.c b/src/tests/util-tests.c
index 3d42f0193a677200d5cb4a46805892bed978305c..bfdf078027250b8ff0ce0da2d37fbb20f391d06b 100644
--- a/src/tests/util-tests.c
+++ b/src/tests/util-tests.c
@@ -406,6 +406,15 @@ START_TEST(test_sss_filter_sanitize)
                 "Expected [%s], got [%s]",
                 has_all_expected, sanitized);
 
+    /* Input is reused from previous test - "\\(user)*name" */
+    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 3d90cf0d1024b93016987a4d3e8a515359fd974d..94a3ddea839f0998cb7796f1d2fe13f743de3aaf 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 7e58e6b481b07fad6a98a4a6c5084ddcac361fea 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     | 50 ++++++++++++++++++++++++++++++++++++++--
 2 files changed, 51 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 3245e1b12a69483f961f01210d13654b1c7c5345..61f09fc41d3210af5044f5338dd90db67e0123a7 100644
--- a/src/providers/ldap/ldap_id.c
+++ b/src/providers/ldap/ldap_id.c
@@ -114,6 +114,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;
@@ -388,6 +396,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;
+
+    if (state->filter_type == BE_FILTER_WILDCARD) {
+        multiple_results = true;
+    } else {
+        multiple_results = false;
+    }
 
     subreq = sdap_get_users_send(state, state->ev,
                                  state->domain, state->sysdb,
@@ -397,7 +412,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;
@@ -508,6 +523,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;
@@ -619,6 +641,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;
 
@@ -871,6 +901,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;
+
+    if (state->filter_type == BE_FILTER_WILDCARD) {
+        multiple_results = true;
+    } else {
+        multiple_results = false;
+    }
 
     subreq = sdap_get_groups_send(state, state->ev,
                                   state->sdom,
@@ -879,7 +916,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;
@@ -953,6 +991,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 groups 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 6d2143196b6709f3a0f6858392a0b9af4b4058fb 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 aa44adf105da54ae6ed7a209fd503242d83a3479 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 15eeb29f4811ccbc06c23f5c5a84d4f566e998ef 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 61f09fc41d3210af5044f5338dd90db67e0123a7..73840d2885ed15a7a9dae2e9175d8361c8fdfe7d 100644
--- a/src/providers/ldap/ldap_id.c
+++ b/src/providers/ldap/ldap_id.c
@@ -396,12 +396,12 @@ 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;
     } else {
-        multiple_results = false;
+        lookup_type = SDAP_LOOKUP_SINGLE;
     }
 
     subreq = sdap_get_users_send(state, state->ev,
@@ -412,7 +412,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;
@@ -901,12 +901,12 @@ 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;
     } else {
-        multiple_results = false;
+        lookup_type = SDAP_LOOKUP_SINGLE;
     }
 
     subreq = sdap_get_groups_send(state, state->ev,
@@ -916,7 +916,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 3396f4a50cfc34ea6c3fb61147dfbadef566487e 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 4d45e42af4fa32717caa69cc621834cdffc27431..4b519eddd04cde83c209f5a1940832cc7f41c736 100644
--- a/src/config/SSSDConfig/__init__.py.in
+++ b/src/config/SSSDConfig/__init__.py.in
@@ -355,6 +355,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 faab3a51e54d6d498392021a8945501120870f70..b636d93108ef0a3831970d7827895c14b0f3571c 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 cfcc00f6f7ca768df861e8cf7face065f90e9e83..ab712fe55cdac6d247a085aeca5cc82d65966623 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
+wildcard_limit = int, 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 c10290217b1b133792b893d9b80e2599969838a6..8fd45fd4093714f458161eb352157c845d926f06 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
+wildcard_limit = int, 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 f14090843fd32141ad4f491b69868aa7b2412301..9ac175f8d4a8aa01ca2434b800ebae1be88575f5 100644
--- a/src/man/sssd-ldap.5.xml
+++ b/src/man/sssd-ldap.5.xml
@@ -2135,6 +2135,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 cb4c05d846d9abe5eedb28013ad13fff6476d431..d685edcb44c771b0afc7a232a82c21fc9d1c89f9 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 253c0715355536cc181c57beed5326a77e87e464..9576228d1bf3424c8867bda058b59c3ca6b2216b 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 c1b9bf688ef0a92046195c13a11d2c17b2419d67..9f58db5bd9eef1391e97c1890cbff94c2a5406d6 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 d9b2d18637bdb3e7823af9d1de2c042c8134780f..444502bf7159edcf4cebe530cce8b216c737ec30 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 da0aba106262f067be848d614f9871c2595468bf 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 |  22 +++++
 src/responder/ifp/ifp_users.c   | 184 ++++++++++++++++++++++++++++++++++++++++
 src/responder/ifp/ifpsrv.c      |  23 +++++
 src/responder/ifp/ifpsrv_util.c |  52 ++++++++++++
 7 files changed, 472 insertions(+)

diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h
index b2ec2e0b98a9be2d50009df524a1072e9b1c15c7..36df6aea268cc5c82696f20b1a65963350d5e100 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 1b581b568f14362a47b4a80eb55d2de8eb936ae3..3060035924026641cc245f2a1970db9e2646e11c 100644
--- a/src/responder/ifp/ifp_groups.c
+++ b/src/responder/ifp/ifp_groups.c
@@ -81,6 +81,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_remaining_capacity(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,
@@ -221,23 +242,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 304e4dc535aac4215cf318a0bea845c161c5f079..43519de6fef3033f1e47cecb787d6b02dc9c6e56 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,
@@ -84,4 +85,25 @@ 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_remaining_capacity(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 2ec74c30b348ac5f2b84cdc8e2dd406fd44a7da3..effefdc0435d794206dbe7358c61d2ea47760361 100644
--- a/src/responder/ifp/ifp_users.c
+++ b/src/responder/ifp/ifp_users.c
@@ -309,23 +309,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_remaining_capacity(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_remaining_capacity(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 631bcd266d7e06154dbf1f37f9f439119b2b8944..cdc411faa330dc2c063e52abe63cd68dbe16a5d9 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"
@@ -228,6 +229,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,
@@ -321,6 +323,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..3b02fd06f5227e4ffc3d40ffb20fed981c5028a7 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,53 @@ 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_remaining_capacity(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