dpward / rpms / sssd

Forked from rpms/sssd 3 years ago
Clone
Blob Blame History Raw
From fe9a0097970d12ff261b7417f9e57db95957ab24 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Wed, 17 Jun 2015 13:39:43 +0200
Subject: [PATCH 12/13] IFP: Add wildcard requests
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

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.

Reviewed-by: Pavel Březina <pbrezina@redhat.com>
---
 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