/*
    SSSD

    Authors:
        Samuel Cabrero <scabrero@suse.com>

    Copyright (C) 2019 SUSE LINUX GmbH, Nuernberg, Germany.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <arpa/inet.h>

#include "db/sysdb.h"
#include "db/sysdb_private.h"
#include "db/sysdb_iphosts.h"

static errno_t
sysdb_host_update(struct sysdb_ctx *sysdb,
                  struct ldb_dn *dn,
                  const char **aliases,
                  const char **addresses);

static errno_t
sysdb_host_remove_alias(struct sysdb_ctx *sysdb,
                        struct ldb_dn *dn,
                        const char *alias);

errno_t
sysdb_gethostbyname(TALLOC_CTX *mem_ctx,
                    struct sss_domain_info *domain,
                    const char *name,
                    struct ldb_result **_res)
{
    errno_t ret;
    TALLOC_CTX *tmp_ctx;
    static const char *attrs[] = {
        SYSDB_NAME,
        SYSDB_NAME_ALIAS,
        SYSDB_IP_HOST_ATTR_ADDRESS,
        SYSDB_DEFAULT_ATTRS,
        NULL,
    };
    char *sanitized_name;
    char *subfilter;
    struct ldb_result *res = NULL;
    struct ldb_message **msgs;
    size_t msgs_count;

    DEBUG(SSSDBG_TRACE_FUNC, "Searching host by name [%s] in domain [%s]\n",
          name, domain->name);

    tmp_ctx = talloc_new(NULL);
    if (tmp_ctx == NULL) {
        return ENOMEM;
    }

    ret = sss_filter_sanitize(tmp_ctx, name, &sanitized_name);
    if (ret != EOK) {
        goto done;
    }

    subfilter = talloc_asprintf(tmp_ctx, SYSDB_IP_HOST_BYNAME_SUBFILTER,
                                sanitized_name, sanitized_name);
    if (subfilter == NULL) {
        ret = ENOMEM;
        goto done;
    }

    ret = sysdb_search_hosts(mem_ctx, domain, subfilter,
                             attrs, &msgs_count, &msgs);
    if (ret == EOK) {
        res = talloc_zero(mem_ctx, struct ldb_result);
        if (res == NULL) {
            ret = ENOMEM;
            goto done;
        }
        res->count = msgs_count;
        res->msgs = talloc_steal(res, msgs);
    }

    *_res = res;

done:
    talloc_free(tmp_ctx);

    return ret;
}

errno_t sysdb_gethostbyaddr(TALLOC_CTX *mem_ctx,
                            struct sss_domain_info *domain,
                            const char *address,
                            struct ldb_result **_res)
{
    errno_t ret;
    TALLOC_CTX *tmp_ctx;
    static const char *attrs[] = {
        SYSDB_NAME,
        SYSDB_NAME_ALIAS,
        SYSDB_IP_HOST_ATTR_ADDRESS,
        NULL,
    };
    char *sanitized_address;
    char *subfilter;
    struct ldb_result *res = NULL;
    struct ldb_message **msgs;
    size_t msgs_count;
    char *canonical_address;

    tmp_ctx = talloc_new(NULL);
    if (tmp_ctx == NULL) {
        return ENOMEM;
    }

    /* IP addresses are stored in canonical form, canonicalize the given
     * address before search */
    ret = sss_canonicalize_ip_address(tmp_ctx, address, &canonical_address);
    if (ret != EOK) {
        goto done;
    }

    DEBUG(SSSDBG_TRACE_FUNC, "Searching host by address [%s] in domain [%s]\n",
          canonical_address, domain->name);

    ret = sss_filter_sanitize(tmp_ctx, canonical_address, &sanitized_address);
    if (ret != EOK) {
        goto done;
    }

    subfilter = talloc_asprintf(tmp_ctx, SYSDB_IP_HOST_BYADDR_SUBFILTER,
                                sanitized_address);
    if (subfilter == NULL) {
        ret = ENOMEM;
        goto done;
    }

    ret = sysdb_search_hosts(mem_ctx, domain, subfilter,
                             attrs, &msgs_count, &msgs);
    if (ret == EOK) {
        res = talloc_zero(mem_ctx, struct ldb_result);
        if (res == NULL) {
            ret = ENOMEM;
            goto done;
        }
        res->count = msgs_count;
        res->msgs = talloc_steal(res, msgs);
    }

    *_res = res;

done:
    talloc_free(tmp_ctx);

    return ret;
}

errno_t
sysdb_store_host(struct sss_domain_info *domain,
                 const char *primary_name,
                 const char **aliases,
                 const char **addresses,
                 struct sysdb_attrs *extra_attrs,
                 char **remove_attrs,
                 uint64_t cache_timeout,
                 time_t now)
{
    errno_t ret;
    errno_t sret;
    TALLOC_CTX *tmp_ctx;
    bool in_transaction = false;
    struct ldb_result *res = NULL;
    const char *name;
    unsigned int i, j;
    struct ldb_dn *update_dn = NULL;
    struct sysdb_attrs *attrs;
    struct sysdb_ctx *sysdb;
    size_t len;

    DEBUG(SSSDBG_TRACE_FUNC, "Storing host [%s] into cache, domain [%s]\n",
          primary_name, domain->name);

    tmp_ctx = talloc_new(NULL);
    if (tmp_ctx == NULL) {
        return ENOMEM;
    }

    sysdb = domain->sysdb;

    ret = sysdb_transaction_start(sysdb);
    if (ret != EOK) {
        DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n");
        goto done;
    }

    in_transaction = true;

    /* Check that the addresses are unique. If the address appears for any
     * host other than the one matching the primary_name, we need to
     * remove them so that gethostbyaddr() can work properly.
     * Last entry saved to the cache should always "win".
     */
    len = talloc_array_length(addresses);
    for (i = 0; i < len && addresses[i] != NULL; i++) {
        ret = sysdb_gethostbyaddr(tmp_ctx, domain, addresses[i], &res);
        if (ret != EOK && ret != ENOENT) {
            goto done;
        } else if (ret != ENOENT) {
            if (res->count != 1) {
                /* Somehow the cache has multiple entries with  the same
                 * address. This is corrupted. We'll delete them all to
                 * sort it out.
                 */
                for (j = 0; j < res->count; j++) {
                    DEBUG(SSSDBG_CRIT_FAILURE,
                          "Corrupt cache entry [%s] detected. Deleting\n",
                           ldb_dn_canonical_string(tmp_ctx,
                                                   res->msgs[j]->dn));

                    ret = sysdb_delete_entry(sysdb, res->msgs[j]->dn, true);
                    if (ret != EOK) {
                        DEBUG(SSSDBG_OP_FAILURE,
                              "Could not delete corrupt cache entry [%s]\n",
                               ldb_dn_canonical_string(tmp_ctx,
                                                       res->msgs[j]->dn));
                        goto done;
                    }
                }
            } else {
                /* Check whether this is the same name as we're currently
                 * saving to the cache.
                 */
                name = ldb_msg_find_attr_as_string(res->msgs[0],
                                                   SYSDB_NAME,
                                                   NULL);
                if (name == NULL || strcmp(name, primary_name) != 0) {
                    if (name == NULL) {
                        DEBUG(SSSDBG_CRIT_FAILURE, "A host with no name?\n");
                        /* Corrupted */
                    }

                    /* Either this is a corrupt entry or it's another host
                     * claiming ownership of this address. In order to account
                     * for address reassignments, we need to delete the old
                     * entry.
                     */
                    DEBUG(SSSDBG_TRACE_FUNC,
                          "Corrupt or replaced cache entry [%s] detected. "
                           "Deleting\n",
                           ldb_dn_canonical_string(tmp_ctx,
                                                   res->msgs[0]->dn));

                    ret = sysdb_delete_entry(sysdb, res->msgs[0]->dn, true);
                    if (ret != EOK) {
                        DEBUG(SSSDBG_OP_FAILURE,
                              "Could not delete cache entry [%s]\n",
                               ldb_dn_canonical_string(tmp_ctx,
                                                       res->msgs[0]->dn));
                    }
                }
            }
        }
        talloc_zfree(res);
    }

    /* Ok, addresses should now be unique. Now look the host up by name
     * to determine if we need to update existing entries or modify aliases.
     */
    ret = sysdb_gethostbyname(tmp_ctx, domain, primary_name, &res);
    if (ret != EOK && ret != ENOENT) {
        goto done;
    } else if (ret != ENOENT) { /* Found entries */
        for (i = 0; i < res->count; i++) {
            /* Check whether this is the same name as we're currently
             * saving to the cache.
             */
            name = ldb_msg_find_attr_as_string(res->msgs[i],
                                               SYSDB_NAME,
                                               NULL);
            if (name == NULL) {
                /* Corrupted */
                DEBUG(SSSDBG_CRIT_FAILURE,
                      "A host with no name?\n");
                DEBUG(SSSDBG_TRACE_FUNC,
                      "Corrupt cache entry [%s] detected. Deleting\n",
                       ldb_dn_canonical_string(tmp_ctx,
                                               res->msgs[i]->dn));

                ret = sysdb_delete_entry(sysdb, res->msgs[i]->dn, true);
                if (ret != EOK) {
                    DEBUG(SSSDBG_OP_FAILURE,
                          "Could not delete corrupt cache entry [%s]\n",
                           ldb_dn_canonical_string(tmp_ctx,
                                                   res->msgs[i]->dn));
                    goto done;
                }
            } else if (strcmp(name, primary_name) == 0) {
                /* This is the same host name, so we need
                 * to update this entry with the values
                 * provided.
                 */
                if (update_dn) {
                    DEBUG(SSSDBG_CRIT_FAILURE,
                          "Two existing hosts with the same name: [%s]? "
                           "Deleting both.\n",
                           primary_name);

                    /* Delete the entry from the previous pass */
                    ret = sysdb_delete_entry(sysdb, update_dn, true);
                    if (ret != EOK) {
                        DEBUG(SSSDBG_OP_FAILURE,
                              "Could not delete cache entry [%s]\n",
                               ldb_dn_canonical_string(tmp_ctx,
                                                       update_dn));
                        goto done;
                    }

                    /* Delete the new entry as well */
                    ret = sysdb_delete_entry(sysdb, res->msgs[i]->dn, true);
                    if (ret != EOK) {
                        DEBUG(SSSDBG_MINOR_FAILURE,
                              "Could not delete cache entry [%s]\n",
                               ldb_dn_canonical_string(tmp_ctx,
                                                       res->msgs[i]->dn));
                        goto done;
                    }

                    update_dn = NULL;
                } else {
                    update_dn = talloc_steal(tmp_ctx, res->msgs[i]->dn);
                }
            } else {
                /* Another host is claiming this name as an alias.
                 * In order to account for aliases being promoted to
                 * primary names, we need to make sure to remove the
                 * old alias entry.
                 */
                ret = sysdb_host_remove_alias(sysdb,
                                              res->msgs[i]->dn,
                                              primary_name);
                if (ret != EOK) {
                    goto done;
                }
            }
        }
        talloc_zfree(res);
    }

    if (update_dn) {
        /* Update the existing entry */
        ret = sysdb_host_update(sysdb, update_dn, aliases, addresses);
    } else {
        /* Add a new entry */
        ret = sysdb_host_add(tmp_ctx, domain, primary_name,
                             aliases, addresses, &update_dn);
    }

    if (ret != EOK) {
        goto done;
    }

    if (extra_attrs == NULL) {
        attrs = sysdb_new_attrs(tmp_ctx);
        if (attrs == NULL) {
            ret = ENOMEM;
            goto done;
        }
    } else {
        attrs = extra_attrs;
    }

    ret = sysdb_attrs_add_time_t(attrs, SYSDB_LAST_UPDATE, now);
    if (ret) {
        goto done;
    }

    ret = sysdb_attrs_add_time_t(attrs, SYSDB_CACHE_EXPIRE,
                                 ((cache_timeout) ?
                                  (now + cache_timeout) : 0));
    if (ret) {
        goto done;
    }

    ret = sysdb_set_entry_attr(sysdb, update_dn, attrs, SYSDB_MOD_REP);
    if (ret != EOK) {
        goto done;
    }

    if (remove_attrs) {
        ret = sysdb_remove_attrs(domain, primary_name,
                                 SYSDB_MEMBER_HOST,
                                 remove_attrs);
        if (ret != EOK) {
            DEBUG(SSSDBG_MINOR_FAILURE,
                  "Could not remove missing attributes: [%s]\n",
                   strerror(ret));
            goto done;
        }
    }

    ret = sysdb_transaction_commit(sysdb);
    if (ret != EOK) {
        DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n");
        goto done;
    }
    in_transaction = false;

done:
    if (in_transaction) {
        sret = sysdb_transaction_cancel(sysdb);
        if (sret != EOK) {
            DEBUG(SSSDBG_CRIT_FAILURE, "Could not cancel transaction\n");
        }
    }
    talloc_free(tmp_ctx);
    return ret;
}
struct ldb_dn *sysdb_host_dn(TALLOC_CTX *mem_ctx,
                             struct sss_domain_info *domain,
                             const char *name)
{
    errno_t ret;
    char *clean_name;
    struct ldb_dn *dn;

    ret = sysdb_dn_sanitize(NULL, name, &clean_name);
    if (ret != EOK) {
        return NULL;
    }

    dn = ldb_dn_new_fmt(mem_ctx, domain->sysdb->ldb, SYSDB_TMPL_IP_HOST,
                        clean_name, domain->name);
    talloc_free(clean_name);

    return dn;
}

errno_t sysdb_host_add(TALLOC_CTX *mem_ctx,
                       struct sss_domain_info *domain,
                       const char *primary_name,
                       const char **aliases,
                       const char **addresses,
                       struct ldb_dn **dn)
{
    errno_t ret;
    int lret;
    TALLOC_CTX *tmp_ctx;
    struct ldb_message *msg;
    unsigned long i;
    size_t len;

    DEBUG(SSSDBG_TRACE_FUNC, "Adding host [%s] to domain [%s]\n",
          primary_name, domain->name);

    tmp_ctx = talloc_new(NULL);
    if (tmp_ctx == NULL) {
        return ENOMEM;
    }

    msg = ldb_msg_new(tmp_ctx);
    if (msg == NULL) {
        ret = ENOMEM;
        goto done;
    }

    /* host dn */
    msg->dn = sysdb_host_dn(msg, domain, primary_name);
    if (msg->dn == NULL) {
        ret = ENOMEM;
        goto done;
    }

    /* Objectclass */
    ret = sysdb_add_string(msg, SYSDB_OBJECTCLASS, SYSDB_HOST_CLASS);
    if (ret != EOK) {
        goto done;
    }

    /* Set the primary name */
    ret = sysdb_add_string(msg, SYSDB_NAME, primary_name);
    if (ret != EOK) {
        goto done;
    }

    /* If this iphost has any aliases, include them */
    if (aliases != NULL && aliases[0] != NULL) {
        /* Set the name aliases */
        lret = ldb_msg_add_empty(msg, SYSDB_NAME_ALIAS,
                                 LDB_FLAG_MOD_ADD, NULL);
        if (lret != LDB_SUCCESS) {
            ret = sysdb_error_to_errno(lret);
            goto done;
        }

        len = talloc_array_length(aliases);
        for (i = 0; i < len && aliases[i] != NULL ; i++) {
            lret = ldb_msg_add_string(msg, SYSDB_NAME_ALIAS, aliases[i]);
            if (lret != LDB_SUCCESS) {
                ret = sysdb_error_to_errno(lret);
                goto done;
            }
        }
    }

    /* Set the addresses */
    lret = ldb_msg_add_empty(msg, SYSDB_IP_HOST_ATTR_ADDRESS,
                             LDB_FLAG_MOD_ADD, NULL);
    if (lret != LDB_SUCCESS) {
        ret = sysdb_error_to_errno(lret);
        goto done;
    }

    len = talloc_array_length(addresses);
    for (i = 0; i < len && addresses[i] != NULL; i++) {
        char *addr = NULL;

        ret = sss_canonicalize_ip_address(msg, addresses[i], &addr);
        if (ret != EOK) {
            DEBUG(SSSDBG_CRIT_FAILURE,
                  "Failed to canonicalize address [%s]: %s\n",
                  addresses[i], sss_strerror(ret));
            goto done;
        }

        lret = ldb_msg_add_string(msg, SYSDB_IP_HOST_ATTR_ADDRESS, addr);
        if (lret != LDB_SUCCESS) {
            ret = sysdb_error_to_errno(lret);
            goto done;
        }
    }

    /* creation time */
    ret = sysdb_add_ulong(msg, SYSDB_CREATE_TIME, (unsigned long)time(NULL));
    if (ret != 0) {
        goto done;
    }

    lret = ldb_add(domain->sysdb->ldb, msg);
    ret = sysdb_error_to_errno(lret);

    if (ret == EOK && dn != NULL) {
        *dn = talloc_steal(mem_ctx, msg->dn);
    }

done:
    if (ret != 0) {
        DEBUG(SSSDBG_TRACE_INTERNAL,
              "Error: %d (%s)\n", ret, strerror(ret));
    }
    talloc_free(tmp_ctx);
    return ret;
}

static errno_t
sysdb_host_update(struct sysdb_ctx *sysdb,
                  struct ldb_dn *dn,
                  const char **aliases,
                  const char **addresses)
{
    errno_t ret;
    struct ldb_message *msg;
    int lret;
    unsigned int i;
    size_t len;

    if (dn == NULL || addresses[0] == NULL) {
        return EINVAL;
    }

    msg = ldb_msg_new(NULL);
    if (msg == NULL) {
        ret = ENOMEM;
        goto done;
    }

    msg->dn = dn;

    if (aliases != NULL && aliases[0] != NULL) {
        /* Update the aliases */
        lret = ldb_msg_add_empty(msg, SYSDB_NAME_ALIAS, SYSDB_MOD_REP, NULL);
        if (lret != LDB_SUCCESS) {
            ret = ENOMEM;
            goto done;
        }

        len = talloc_array_length(aliases);
        for (i = 0; i < len && aliases[i] != NULL; i++) {
            lret = ldb_msg_add_string(msg, SYSDB_NAME_ALIAS, aliases[i]);
            if (lret != LDB_SUCCESS) {
                ret = EINVAL;
                goto done;
            }
        }
    }

    /* Update the addresses */
    lret = ldb_msg_add_empty(msg, SYSDB_IP_HOST_ATTR_ADDRESS,
                             SYSDB_MOD_REP, NULL);
    if (lret != LDB_SUCCESS) {
        ret = ENOMEM;
        goto done;
    }

    len = talloc_array_length(addresses);
    for (i = 0; i < len && addresses[i] != NULL; i++) {
        lret = ldb_msg_add_string(msg, SYSDB_IP_HOST_ATTR_ADDRESS,
                                  addresses[i]);
        if (lret != LDB_SUCCESS) {
            ret = EINVAL;
            goto done;
        }
    }

    lret = ldb_modify(sysdb->ldb, msg);
    if (lret != LDB_SUCCESS) {
        DEBUG(SSSDBG_MINOR_FAILURE,
              "ldb_modify failed: [%s](%d)[%s]\n",
              ldb_strerror(lret), lret, ldb_errstring(sysdb->ldb));
    }
    ret = sysdb_error_to_errno(lret);

done:
    if (ret) {
        DEBUG(SSSDBG_TRACE_INTERNAL,
              "Error: %d (%s)\n", ret, strerror(ret));
    }
    talloc_free(msg);
    return ret;
}


errno_t
sysdb_host_remove_alias(struct sysdb_ctx *sysdb,
                        struct ldb_dn *dn,
                        const char *alias)
{
    errno_t ret;
    struct ldb_message *msg;
    int lret;

    msg = ldb_msg_new(NULL);
    if (msg == NULL) {
        ret = ENOMEM;
        goto done;
    }

    msg->dn = dn;

    ret = sysdb_delete_string(msg, SYSDB_NAME_ALIAS, alias);
    if (ret != EOK) {
        goto done;
    }

    lret = ldb_modify(sysdb->ldb, msg);
    if (lret != LDB_SUCCESS) {
        DEBUG(SSSDBG_MINOR_FAILURE,
              "ldb_modify failed: [%s](%d)[%s]\n",
              ldb_strerror(lret), lret, ldb_errstring(sysdb->ldb));
    }
    ret = sysdb_error_to_errno(lret);

done:
    if (ret) {
        DEBUG(SSSDBG_TRACE_INTERNAL,
              "Error: %d (%s)\n", ret, strerror(ret));
    }
    talloc_zfree(msg);
    return ret;
}


errno_t sysdb_host_delete(struct sss_domain_info *domain,
                          const char *name,
                          const char *address)
{
    errno_t ret, sret;
    TALLOC_CTX *tmp_ctx;
    struct ldb_result *res;
    unsigned int i;
    bool in_transaction = false;
    struct sysdb_ctx *sysdb = domain->sysdb;

    DEBUG(SSSDBG_TRACE_FUNC, "Deleting host [%s] - [%s] from domain [%s]\n",
          name, address, domain->name);

    tmp_ctx = talloc_new(NULL);
    if (tmp_ctx == NULL) {
        return ENOMEM;
    }

    ret = sysdb_transaction_start(sysdb);
    if (ret != EOK) {
        DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n");
        goto done;
    }

    in_transaction = true;

    if (name != NULL) {
        ret = sysdb_gethostbyname(tmp_ctx, domain, name, &res);
        if (ret != EOK && ret != ENOENT) {
            goto done;
        }

        if (ret == ENOENT) {
            /* Doesn't exist in the DB. Nothing to do */
            ret = EOK;
            goto done;
        }
    } else if (address != NULL) {
        ret = sysdb_gethostbyaddr(tmp_ctx, domain, address, &res);
        if (ret != EOK && ret != ENOENT) {
            goto done;
        }

        if (ret == ENOENT) {
            /* Doesn't exist in the DB. Nothing to do */
            ret = EOK;
            goto done;
        }
    } else {
        DEBUG(SSSDBG_CRIT_FAILURE, "Host name or address needed\n");
        ret = EINVAL;
        goto done;
    }

    /* There should only be one matching entry,
     * but if there are multiple, we should delete
     * them all to de-corrupt the DB.
     */
    for (i = 0; i < res->count; i++) {
        ret = sysdb_delete_entry(sysdb, res->msgs[i]->dn, false);
        if (ret != EOK) {
            goto done;
        }
    }

    ret = sysdb_transaction_commit(sysdb);
    if (ret != EOK) {
        DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n");
        goto done;
    }
    in_transaction = false;

done:
    if (in_transaction) {
        sret = sysdb_transaction_cancel(sysdb);
        if (sret != EOK) {
            DEBUG(SSSDBG_CRIT_FAILURE,
                  "Could not cancel transaction\n");
        }
    }

    if (ret != EOK && ret != ENOENT) {
        DEBUG(SSSDBG_TRACE_INTERNAL,
              "Error: %d (%s)\n", ret, strerror(ret));
    }
    talloc_free(tmp_ctx);
    return ret;
}

errno_t sysdb_search_hosts(TALLOC_CTX *mem_ctx,
                           struct sss_domain_info *domain,
                           const char *sub_filter,
                           const char **attrs,
                           size_t *msgs_count,
                           struct ldb_message ***msgs)
{
    TALLOC_CTX *tmp_ctx;
    struct ldb_dn *basedn;
    char *filter;
    int ret;

    DEBUG(SSSDBG_TRACE_FUNC, "Searching hosts with subfilter [%s] in "
          "domain [%s]\n", sub_filter, domain->name);

    tmp_ctx = talloc_new(NULL);
    if (tmp_ctx == NULL) {
        DEBUG(SSSDBG_OP_FAILURE, "Failed to allocate memory\n");
        return ENOMEM;
    }

    basedn = ldb_dn_new_fmt(tmp_ctx, domain->sysdb->ldb,
                            SYSDB_TMPL_IP_HOST_BASE, domain->name);
    if (basedn == NULL) {
        DEBUG(SSSDBG_OP_FAILURE, "Failed to build base dn\n");
        ret = ENOMEM;
        goto fail;
    }

    filter = talloc_asprintf(tmp_ctx, "(&(%s)%s)", SYSDB_IP_HOST_CLASS_FILTER,
                             sub_filter);
    if (filter == NULL) {
        DEBUG(SSSDBG_OP_FAILURE, "Failed to build filter\n");
        ret = ENOMEM;
        goto fail;
    }

    DEBUG(SSSDBG_TRACE_INTERNAL, "Searching hosts with filter [%s] in "
          "domain [%s]\n", filter, domain->name);

    ret = sysdb_search_entry(mem_ctx, domain->sysdb, basedn,
                             LDB_SCOPE_SUBTREE, filter, attrs,
                             msgs_count, msgs);
    if (ret) {
        goto fail;
    }

    talloc_free(tmp_ctx);

    return EOK;

fail:
    if (ret == ENOENT) {
        DEBUG(SSSDBG_TRACE_INTERNAL, "No such entry\n");
    }
    else if (ret) {
        DEBUG(SSSDBG_MINOR_FAILURE, "Error: %d (%s)\n", ret, strerror(ret));
    }

    talloc_free(tmp_ctx);

    return ret;
}

errno_t
sysdb_enumhostent(TALLOC_CTX *mem_ctx,
                  struct sss_domain_info *domain,
                  struct ldb_result **_res)
{
    errno_t ret;
    TALLOC_CTX *tmp_ctx;
    static const char *attrs[] = {
        SYSDB_NAME,
        SYSDB_NAME_ALIAS,
        SYSDB_IP_HOST_ATTR_ADDRESS,
        NULL,
    };
    struct ldb_result *res = NULL;
    struct ldb_message **msgs;
    size_t msgs_count;

    tmp_ctx = talloc_new(NULL);
    if (tmp_ctx == NULL) {
        return ENOMEM;
    }

    ret = sysdb_search_hosts(mem_ctx, domain, "",
                             attrs, &msgs_count, &msgs);
    if (ret == EOK) {
        res = talloc_zero(mem_ctx, struct ldb_result);
        if (res == NULL) {
            ret = ENOMEM;
            goto done;
        }
        res->count = msgs_count;
        res->msgs = talloc_steal(res, msgs);
    }

    *_res = res;

done:
    talloc_free(tmp_ctx);
    return ret;
}
