/*
    SSSD

    Async LDAP Helper routines

    Copyright (C) Simo Sorce <ssorce@redhat.com> - 2009

    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 <ctype.h>
#include "util/util.h"
#include "util/strtonum.h"
#include "util/probes.h"
#include "util/sss_chain_id.h"
#include "providers/ldap/sdap_async_private.h"

#define REPLY_INITIAL_SIZE 10

struct sdap_op {
    struct sdap_op *prev, *next;
    struct sdap_handle *sh;
    uint64_t chain_id;

    int msgid;
    char *stat_info;
    uint64_t start_time;
    int timeout;
    bool done;

    sdap_op_callback_t *callback;
    void *data;

    struct tevent_context *ev;
    struct sdap_msg *list;
    struct sdap_msg *last;
};

int sdap_op_get_msgid(struct sdap_op *op)
{
    return op != NULL ? op->msgid : 0;
}

/* ==LDAP-Memory-Handling================================================= */

static int lmsg_destructor(void *mem)
{
    ldap_msgfree((LDAPMessage *)mem);
    return 0;
}

/* ==sdap-handle-utility-functions======================================== */

static inline void sdap_handle_release(struct sdap_handle *sh);
static int sdap_handle_destructor(void *mem);

struct sdap_handle *sdap_handle_create(TALLOC_CTX *memctx)
{
    struct sdap_handle *sh;

    sh = talloc_zero(memctx, struct sdap_handle);
    if (!sh) return NULL;

    talloc_set_destructor((TALLOC_CTX *)sh, sdap_handle_destructor);

    return sh;
}

static int sdap_handle_destructor(void *mem)
{
    struct sdap_handle *sh = talloc_get_type(mem, struct sdap_handle);

    /* if the structure is currently locked, then mark it to be released
     * and prevent talloc from freeing the memory */
    if (sh->destructor_lock) {
        sh->release_memory = true;
        return -1;
    }

    sdap_handle_release(sh);
    return 0;
}

static void sdap_call_op_callback(struct sdap_op *op, struct sdap_msg *reply,
                                  int error)
{
    uint64_t time_spend;
    const char *info = (op->stat_info == NULL ? "-" : op->stat_info);

    if (op->start_time != 0) {
        time_spend = get_spend_time_us(op->start_time);
        DEBUG(SSSDBG_PERF_STAT,
              "Handling LDAP operation [%d][%s] took %s.\n",
              op->msgid, info, sss_format_time(time_spend));

        /* time_spend is in us and timeout in s */
        if (op->timeout != 0 && (time_spend / op->timeout) >= (80 * 10000)) {
            DEBUG(SSSDBG_IMPORTANT_INFO, "LDAP operation [%d][%s] seems slow, "
                                         "took more than 80%% of timeout [%d].\n",
                                         op->msgid, info, op->timeout);
        }

        /* Avoid multiple outputs for the same operation if multiple results
         * are returned */
        op->start_time = 0;
    }

    op->callback(op, reply, error, op->data);
}

static void sdap_handle_release(struct sdap_handle *sh)
{
    struct sdap_op *op;

    DEBUG(SSSDBG_TRACE_INTERNAL,
          "Trace: sh[%p], connected[%d], ops[%p], ldap[%p], "
              "destructor_lock[%d], release_memory[%d]\n",
              sh, (int)sh->connected, sh->ops, sh->ldap,
              (int)sh->destructor_lock, (int)sh->release_memory);

    if (sh->destructor_lock) return;
    sh->destructor_lock = true;

    /* make sure nobody tries to reuse this connection from now on */
    sh->connected = false;

    remove_ldap_connection_callbacks(sh);

    while (sh->ops) {
        op = sh->ops;
        sdap_call_op_callback(op, NULL, EIO);
        /* calling the callback may result in freeing the op */
        /* check if it is still the same or avoid freeing */
        if (op == sh->ops) talloc_free(op);
    }

    if (sh->ldap) {
        ldap_unbind_ext(sh->ldap, NULL, NULL);
        sh->ldap = NULL;
    }

    /* ok, we have done the job, unlock now */
    sh->destructor_lock = false;

    /* finally if a destructor was ever called, free sh before
     * exiting */
    if (sh->release_memory) {
        /* neutralize the destructor as we already handled
         * all was needed to be released */
        talloc_set_destructor((TALLOC_CTX *)sh, NULL);
        talloc_free(sh);
    }
}

/* ==Parse-Results-And-Handle-Disconnections============================== */
static void sdap_process_message(struct tevent_context *ev,
                                 struct sdap_handle *sh, LDAPMessage *msg);
static void sdap_process_result(struct tevent_context *ev, void *pvt);
static void sdap_process_next_reply(struct tevent_context *ev,
                                    struct tevent_timer *te,
                                    struct timeval tv, void *pvt);

void sdap_ldap_result(struct tevent_context *ev, struct tevent_fd *fde,
                      uint16_t flags, void *pvt)
{
    sdap_process_result(ev, pvt);
}

static void sdap_ldap_next_result(struct tevent_context *ev,
                                  struct tevent_timer *te,
                                  struct timeval tv, void *pvt)
{
    sdap_process_result(ev, pvt);
}

static struct sdap_op *sdap_get_message_op(struct sdap_handle *sh,
                                           LDAPMessage *msg)
{
    struct sdap_op *op;
    int msgid;

    msgid = ldap_msgid(msg);
    if (msgid == -1) {
        DEBUG(SSSDBG_OP_FAILURE, "Invalid message id!\n");
        return NULL;
    }

    for (op = sh->ops; op; op = op->next) {
        if (op->msgid == msgid) {
            return op;
        }
    }

    return NULL;
}

static void sdap_process_result(struct tevent_context *ev, void *pvt)
{
    struct sdap_handle *sh = talloc_get_type(pvt, struct sdap_handle);
    uint64_t old_chain_id;
    struct timeval no_timeout = {0, 0};
    struct tevent_timer *te;
    struct sdap_op *op;
    LDAPMessage *msg;
    int ret;

    /* This is a top level event, always use chain id 0. We set a proper id
     * later in this function once we can match the reply with an operation. */
    old_chain_id = sss_chain_id_set(0);

    DEBUG_CONDITIONAL(SSSDBG_TRACE_INTERNAL,
          "Trace: sh[%p], connected[%d], ops[%p], ldap[%p]\n",
              sh, (int)sh->connected, sh->ops, sh->ldap);

    if (!sh->connected || !sh->ldap) {
        DEBUG(SSSDBG_OP_FAILURE, "ERROR: LDAP connection is not connected!\n");
        sdap_handle_release(sh);
        return;
    }

    ret = ldap_result(sh->ldap, LDAP_RES_ANY, 0, &no_timeout, &msg);
    if (ret == 0) {
        /* this almost always means we have reached the end of
         * the list of received messages */
        DEBUG_CONDITIONAL(SSSDBG_TRACE_INTERNAL, "Trace: end of ldap_result list\n");
        return;
    }

    if (ret == -1) {
        ldap_get_option(sh->ldap, LDAP_OPT_RESULT_CODE, &ret);
        DEBUG(SSSDBG_OP_FAILURE,
              "ldap_result error: [%s]\n", ldap_err2string(ret));
        sdap_handle_release(sh);
        return;
    }

    /* We don't know if this will be the last result.
     *
     * important: we must do this before actually processing the message
     * because the message processing might even free the sdap_handler
     * so it must be the last operation.
     * FIXME: use tevent_immediate/tevent_queues, when available */
    memset(&no_timeout, 0, sizeof(struct timeval));

    te = tevent_add_timer(ev, sh, no_timeout, sdap_ldap_next_result, sh);
    if (!te) {
        DEBUG(SSSDBG_CRIT_FAILURE,
              "Failed to add critical timer to fetch next result!\n");
    }

    /* Set the chain id if we can match the operation. */
    op = sdap_get_message_op(sh, msg);
    if (op != NULL) {
        sss_chain_id_set(op->chain_id);
    }

    /* now process this message */
    sdap_process_message(ev, sh, msg);

    /* Restore the chain id. */
    sss_chain_id_set(old_chain_id);
}

static const char *sdap_ldap_result_str(int msgtype)
{
    switch (msgtype) {
    case LDAP_RES_BIND:
        return "LDAP_RES_BIND";

    case LDAP_RES_SEARCH_ENTRY:
        return "LDAP_RES_SEARCH_ENTRY";

    case LDAP_RES_SEARCH_REFERENCE:
        return "LDAP_RES_SEARCH_REFERENCE";

    case LDAP_RES_SEARCH_RESULT:
        return "LDAP_RES_SEARCH_RESULT";

    case LDAP_RES_MODIFY:
        return "LDAP_RES_MODIFY";

    case LDAP_RES_ADD:
        return "LDAP_RES_ADD";

    case LDAP_RES_DELETE:
        return "LDAP_RES_DELETE";

    case LDAP_RES_MODDN:
    /* These are the same result
    case LDAP_RES_MODRDN:
    case LDAP_RES_RENAME:
    */
        return "LDAP_RES_RENAME";

    case LDAP_RES_COMPARE:
        return "LDAP_RES_COMPARE";

    case LDAP_RES_EXTENDED:
        return "LDAP_RES_EXTENDED";

    case LDAP_RES_INTERMEDIATE:
        return "LDAP_RES_INTERMEDIATE";

    case LDAP_RES_ANY:
        return "LDAP_RES_ANY";

    case LDAP_RES_UNSOLICITED:
        return "LDAP_RES_UNSOLICITED";

    default:
        /* Unmatched, fall through */
        break;
    }

    /* Unknown result type */
    return "Unknown result type!";
}

/* process a message calling the right operation callback.
 * msg is completely taken care of (including freeing it)
 * NOTE: this function may even end up freeing the sdap_handle
 * so sdap_handle must not be used after this function is called
 */
static void sdap_process_message(struct tevent_context *ev,
                                 struct sdap_handle *sh, LDAPMessage *msg)
{
    struct sdap_msg *reply;
    struct sdap_op *op;
    int msgtype;
    int ret;

    msgtype = ldap_msgtype(msg);

    op = sdap_get_message_op(sh, msg);
    if (op == NULL) {
        DEBUG(SSSDBG_OP_FAILURE,
              "Unmatched msgid, discarding message (type: %0x)\n",
              msgtype);
        ldap_msgfree(msg);
        return;
    }

    /* shouldn't happen */
    if (op->done) {
        DEBUG(SSSDBG_OP_FAILURE,
              "Operation [%p] already handled (type: %0x)\n", op, msgtype);
        ldap_msgfree(msg);
        return;
    }

    DEBUG_CONDITIONAL(SSSDBG_TRACE_ALL,
          "Message type: [%s]\n", sdap_ldap_result_str(msgtype));

    switch (msgtype) {
    case LDAP_RES_SEARCH_ENTRY:
    case LDAP_RES_SEARCH_REFERENCE:
        /* go and process entry */
        break;

    case LDAP_RES_BIND:
    case LDAP_RES_SEARCH_RESULT:
    case LDAP_RES_MODIFY:
    case LDAP_RES_ADD:
    case LDAP_RES_DELETE:
    case LDAP_RES_MODDN:
    case LDAP_RES_COMPARE:
    case LDAP_RES_EXTENDED:
    case LDAP_RES_INTERMEDIATE:
        /* no more results expected with this msgid */
        op->done = true;
        break;

    default:
        /* unknown msg type?? */
        DEBUG(SSSDBG_CRIT_FAILURE,
              "Couldn't figure out the msg type! [%0x]\n", msgtype);
        ldap_msgfree(msg);
        return;
    }

    reply = talloc_zero(op, struct sdap_msg);
    if (!reply) {
        ldap_msgfree(msg);
        ret = ENOMEM;
    } else {
        reply->msg = msg;
        ret = sss_mem_attach(reply, msg, lmsg_destructor);
        if (ret != EOK) {
            ldap_msgfree(msg);
            talloc_zfree(reply);
        }
    }

    if (op->list) {
        /* list exist, queue it */

        op->last->next = reply;
        op->last = reply;

    } else {
        /* create list, then call callback */
        op->list = op->last = reply;

        /* must be the last operation as it may end up freeing all memory
         * including all ops handlers */
        sdap_call_op_callback(op, reply, ret);
    }
}

static void sdap_unlock_next_reply(struct sdap_op *op)
{
    struct timeval tv;
    struct tevent_timer *te;
    struct sdap_msg *next_reply;

    if (op->list) {
        next_reply = op->list->next;
        /* get rid of the previous reply, it has been processed already */
        talloc_zfree(op->list);
        op->list = next_reply;
    }

    /* if there are still replies to parse, queue a new operation */
    if (op->list) {
        /* use a very small timeout, so that fd operations have a chance to be
         * served while processing a long reply */
        tv = tevent_timeval_current();

        /* wait 5 microsecond */
        tv.tv_usec += 5;
        tv.tv_sec += tv.tv_usec / 1000000;
        tv.tv_usec = tv.tv_usec % 1000000;

        te = tevent_add_timer(op->ev, op, tv,
                              sdap_process_next_reply, op);
        if (!te) {
            DEBUG(SSSDBG_CRIT_FAILURE,
                  "Failed to add critical timer for next reply!\n");
            sdap_call_op_callback(op, NULL, EFAULT);
        }
    }
}

static void sdap_process_next_reply(struct tevent_context *ev,
                                    struct tevent_timer *te,
                                    struct timeval tv, void *pvt)
{
    struct sdap_op *op = talloc_get_type(pvt, struct sdap_op);

    sdap_call_op_callback(op, op->list, EOK);
}

/* ==LDAP-Operations-Helpers============================================== */

static int sdap_op_destructor(void *mem)
{
    struct sdap_op *op = (struct sdap_op *)mem;

    DLIST_REMOVE(op->sh->ops, op);

    if (op->done) {
        DEBUG_CONDITIONAL(SSSDBG_TRACE_INTERNAL,
                          "Operation %d finished\n", op->msgid);
        return 0;
    }

    /* we don't check the result here, if a message was really abandoned,
     * hopefully the server will get an abandon.
     * If the operation was already fully completed, this is going to be
     * just a noop */
    DEBUG(SSSDBG_TRACE_LIBS, "Abandoning operation %d\n", op->msgid);
    ldap_abandon_ext(op->sh->ldap, op->msgid, NULL, NULL);

    return 0;
}

static void sdap_op_timeout(struct tevent_req *req)
{
    struct sdap_op *op = tevent_req_callback_data(req, struct sdap_op);

    /* should never happen, but just in case */
    if (op->done) {
        DEBUG(SSSDBG_OP_FAILURE, "Timeout happened after op was finished !?\n");
        return;
    }

    /* signal the caller that we have a timeout */
    DEBUG(SSSDBG_TRACE_LIBS, "Issuing timeout [ldap_opt_timeout] for message id %d\n", op->msgid);
    sdap_call_op_callback(op, NULL, ETIMEDOUT);
}

int sdap_op_add(TALLOC_CTX *memctx, struct tevent_context *ev,
                struct sdap_handle *sh, int msgid, const char *stat_info,
                sdap_op_callback_t *callback, void *data,
                int timeout, struct sdap_op **_op)
{
    struct sdap_op *op;

    op = talloc_zero(memctx, struct sdap_op);
    if (!op) return ENOMEM;

    op->start_time = get_start_time();
    op->timeout = timeout;
    op->sh = sh;
    op->msgid = msgid;
    if (stat_info != NULL) {
        op->stat_info = talloc_strdup(op, stat_info);
        if (op->stat_info == NULL) {
            DEBUG(SSSDBG_OP_FAILURE, "Failed to copy stat_info, ignored.\n");
        }
    }
    op->callback = callback;
    op->data = data;
    op->ev = ev;
    op->chain_id = sss_chain_id_get();

    DEBUG(SSSDBG_TRACE_INTERNAL,
          "New operation %d timeout %d\n", op->msgid, timeout);

    /* check if we need to set a timeout */
    if (timeout) {
        struct tevent_req *req;
        struct timeval tv;

        tv = tevent_timeval_current();
        tv = tevent_timeval_add(&tv, timeout, 0);

        /* allocate on op, so when it get freed the timeout is removed */
        req = tevent_wakeup_send(op, ev, tv);
        if (!req) {
            talloc_zfree(op);
            return ENOMEM;
        }
        tevent_req_set_callback(req, sdap_op_timeout, op);
    }

    DLIST_ADD(sh->ops, op);

    talloc_set_destructor((TALLOC_CTX *)op, sdap_op_destructor);

    *_op = op;
    return EOK;
}

/* ==Modify-Password====================================================== */

static errno_t
sdap_chpass_result(TALLOC_CTX *mem_ctx,
                   int ldap_result,
                   const char *ldap_msg,
                   char **_user_msg)
{
    errno_t ret;

    switch (ldap_result) {
    case LDAP_SUCCESS:
        /* There's no need to set _user_msg here. */
        return EOK;
    case LDAP_CONSTRAINT_VIOLATION:
        if (ldap_msg == NULL || *ldap_msg == '\0') {
            ldap_msg = "Please make sure the password "
                       "meets the complexity constraints.";
        }
        ret = ERR_CHPASS_DENIED;
        break;
    default:
        ret = ERR_NETWORK_IO;
    }

    if (ldap_msg != NULL) {
        *_user_msg = talloc_strdup(mem_ctx, ldap_msg);
        if (*_user_msg == NULL) {
            DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n");
            return ENOMEM;
        }
    }

    return ret;
}

struct sdap_exop_modify_passwd_state {
    struct sdap_handle *sh;

    struct sdap_op *op;

    char *user_error_message;
};

static void sdap_exop_modify_passwd_done(struct sdap_op *op,
                                         struct sdap_msg *reply,
                                         int error, void *pvt);

struct tevent_req *sdap_exop_modify_passwd_send(TALLOC_CTX *memctx,
                                           struct tevent_context *ev,
                                           struct sdap_handle *sh,
                                           char *user_dn,
                                           const char *password,
                                           const char *new_password,
                                           int timeout,
                                           bool use_ppolicy)
{
    struct tevent_req *req = NULL;
    struct sdap_exop_modify_passwd_state *state;
    int ret;
    BerElement *ber = NULL;
    struct berval *bv = NULL;
    int msgid;
    LDAPControl **request_controls = NULL;
    LDAPControl *ctrls[2] = { NULL, NULL };
    char *stat_info;

    req = tevent_req_create(memctx, &state,
                            struct sdap_exop_modify_passwd_state);
    if (!req) return NULL;

    state->sh = sh;
    state->user_error_message = NULL;

    ber = ber_alloc_t( LBER_USE_DER );
    if (ber == NULL) {
        DEBUG(SSSDBG_TRACE_LIBS, "ber_alloc_t failed.\n");
        talloc_zfree(req);
        return NULL;
    }

    ret = ber_printf( ber, "{tststs}", LDAP_TAG_EXOP_MODIFY_PASSWD_ID,
                     user_dn,
                     LDAP_TAG_EXOP_MODIFY_PASSWD_OLD, password,
                     LDAP_TAG_EXOP_MODIFY_PASSWD_NEW, new_password);
    if (ret == -1) {
        DEBUG(SSSDBG_CRIT_FAILURE, "ber_printf failed.\n");
        ber_free(ber, 1);
        talloc_zfree(req);
        return NULL;
    }

    ret = ber_flatten(ber, &bv);
    ber_free(ber, 1);
    if (ret == -1) {
        DEBUG(SSSDBG_CRIT_FAILURE, "ber_flatten failed.\n");
        talloc_zfree(req);
        return NULL;
    }

    if (use_ppolicy) {
        ret = sdap_control_create(state->sh, LDAP_CONTROL_PASSWORDPOLICYREQUEST,
                                  0, NULL, 0, &ctrls[0]);
        if (ret != LDAP_SUCCESS && ret != LDAP_NOT_SUPPORTED) {
            DEBUG(SSSDBG_CRIT_FAILURE, "sdap_control_create failed to create "
                                       "Password Policy control.\n");
            ret = ERR_INTERNAL;
            goto fail;
        }
        request_controls = ctrls;
    }

    DEBUG(SSSDBG_CONF_SETTINGS, "Executing extended operation\n");

    ret = ldap_extended_operation(state->sh->ldap, LDAP_EXOP_MODIFY_PASSWD,
                                  bv, request_controls, NULL, &msgid);
    ber_bvfree(bv);
    if (ctrls[0]) ldap_control_free(ctrls[0]);
    if (ret == -1 || msgid == -1) {
        DEBUG(SSSDBG_CRIT_FAILURE, "ldap_extended_operation failed\n");
        ret = ERR_NETWORK_IO;
        goto fail;
    }
    DEBUG(SSSDBG_TRACE_INTERNAL,
          "ldap_extended_operation sent, msgid = %d\n", msgid);

    stat_info = talloc_asprintf(state, "server: [%s] modify passwd dn: [%s]",
                                sdap_get_server_peer_str_safe(state->sh),
                                user_dn);
    if (stat_info == NULL) {
        DEBUG(SSSDBG_OP_FAILURE, "Failed to create info string, ignored.\n");
    }

    ret = sdap_op_add(state, ev, state->sh, msgid, stat_info,
                      sdap_exop_modify_passwd_done, req, timeout, &state->op);
    if (ret) {
        DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set up operation!\n");
        ret = ERR_INTERNAL;
        goto fail;
    }

    return req;

fail:
    tevent_req_error(req, ret);
    tevent_req_post(req, ev);
    return req;
}

static void sdap_exop_modify_passwd_done(struct sdap_op *op,
                                         struct sdap_msg *reply,
                                         int error, void *pvt)
{
    struct tevent_req *req = talloc_get_type(pvt, struct tevent_req);
    struct sdap_exop_modify_passwd_state *state = tevent_req_data(req,
                                         struct sdap_exop_modify_passwd_state);
    char *errmsg = NULL;
    int ret;
    LDAPControl **response_controls = NULL;
    int c;
    ber_int_t pp_grace;
    ber_int_t pp_expire;
    LDAPPasswordPolicyError pp_error;
    int result;

    if (error) {
        tevent_req_error(req, error);
        return;
    }

    ret = ldap_parse_result(state->sh->ldap, reply->msg,
                            &result, NULL, &errmsg, NULL,
                            &response_controls, 0);
    if (ret != LDAP_SUCCESS) {
        DEBUG(SSSDBG_OP_FAILURE,
              "ldap_parse_result failed (%d)\n", state->op->msgid);
        ret = ERR_INTERNAL;
        goto done;
    }

    if (response_controls == NULL) {
        DEBUG(SSSDBG_FUNC_DATA, "Server returned no controls.\n");
    } else {
        for (c = 0; response_controls[c] != NULL; c++) {
            DEBUG(SSSDBG_TRACE_ALL, "Server returned control [%s].\n",
                      response_controls[c]->ldctl_oid);
            if (strcmp(response_controls[c]->ldctl_oid,
                       LDAP_CONTROL_PASSWORDPOLICYRESPONSE) == 0) {
                ret = ldap_parse_passwordpolicy_control(state->sh->ldap,
                                                        response_controls[c],
                                                        &pp_expire, &pp_grace,
                                                        &pp_error);
                if (ret != LDAP_SUCCESS) {
                    DEBUG(SSSDBG_CRIT_FAILURE,
                          "ldap_parse_passwordpolicy_control failed.\n");
                    ret = ERR_NETWORK_IO;
                    goto done;
                }

                DEBUG(SSSDBG_TRACE_LIBS,
                      "Password Policy Response: expire [%d] grace [%d] "
                          "error [%s].\n", pp_expire, pp_grace,
                          ldap_passwordpolicy_err2txt(pp_error));
            }
        }
    }

    DEBUG(SSSDBG_MINOR_FAILURE, "ldap_extended_operation result: %s(%d), %s\n",
            sss_ldap_err2string(result), result, errmsg);

    ret = sdap_chpass_result(state, result, errmsg, &state->user_error_message);

done:
    ldap_controls_free(response_controls);
    ldap_memfree(errmsg);

    if (ret == EOK) {
        tevent_req_done(req);
    } else {
        tevent_req_error(req, ret);
    }
}

errno_t sdap_exop_modify_passwd_recv(struct tevent_req *req,
                                     TALLOC_CTX * mem_ctx,
                                     char **user_error_message)
{
    struct sdap_exop_modify_passwd_state *state = tevent_req_data(req,
                                         struct sdap_exop_modify_passwd_state);

    /* We want to return the error message even on failure */
    *user_error_message = talloc_steal(mem_ctx, state->user_error_message);

    TEVENT_REQ_RETURN_ON_ERROR(req);

    return EOK;
}

struct sdap_modify_state {
    struct tevent_context *ev;
    struct sdap_handle *sh;
    struct sdap_op *op;

    int ldap_result;
    char *ldap_msg;
};

static void sdap_modify_done(struct sdap_op *op,
                             struct sdap_msg *reply,
                             int error, void *pvt);

static struct tevent_req *
sdap_modify_send(TALLOC_CTX *mem_ctx,
                 struct tevent_context *ev,
                 struct sdap_handle *sh,
                 int timeout,
                 const char *dn,
                 char *attr,
                 char **values)
{
    struct tevent_req *req;
    struct sdap_modify_state *state;
    LDAPMod **mods;
    errno_t ret;
    int msgid;
    char *stat_info;

    req = tevent_req_create(mem_ctx, &state, struct sdap_modify_state);
    if (req == NULL) {
        return NULL;
    }

    state->ev = ev;
    state->sh = sh;

    mods = talloc_zero_array(state, LDAPMod *, 2);
    if (mods == NULL) {
        ret = ENOMEM;
        goto done;
    }

    mods[0] = talloc_zero(mods, LDAPMod);
    if (mods[0] == NULL) {
        ret = ENOMEM;
        goto done;
    }

    mods[0]->mod_op = LDAP_MOD_REPLACE;
    mods[0]->mod_type = attr;
    mods[0]->mod_vals.modv_strvals = values;
    mods[1] = NULL;

    ret = ldap_modify_ext(state->sh->ldap, dn, mods, NULL, NULL, &msgid);
    if (ret != EOK) {
        DEBUG(SSSDBG_CRIT_FAILURE, "ldap_modify_ext() failed [%d]\n", ret);
        goto done;
    }

    stat_info = talloc_asprintf(state, "server: [%s] modify dn: [%s] attr: [%s]",
                                sdap_get_server_peer_str_safe(state->sh), dn,
                                attr);
    if (stat_info == NULL) {
        DEBUG(SSSDBG_OP_FAILURE, "Failed to create info string, ignored.\n");
    }

    ret = sdap_op_add(state, state->ev, state->sh, msgid, stat_info,
                      sdap_modify_done, req, timeout, &state->op);
    if (ret) {
        DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set up operation!\n");
        goto done;
    }

    ret = EOK;

done:
    if (ret != EOK) {
        tevent_req_error(req, ret);
        tevent_req_post(req, ev);
    }

    return req;
}

static void sdap_modify_done(struct sdap_op *op,
                             struct sdap_msg *reply,
                             int error, void *pvt)
{
    struct tevent_req *req;
    struct sdap_modify_state *state;
    char *errmsg;
    errno_t ret;
    int result;
    int lret;

    req = talloc_get_type(pvt, struct tevent_req);
    state = tevent_req_data(req, struct sdap_modify_state);

    if (error) {
        tevent_req_error(req, error);
        return;
    }

    lret = ldap_parse_result(state->sh->ldap, reply->msg, &result,
                             NULL, &errmsg, NULL, NULL, 0);
    if (lret != LDAP_SUCCESS) {
        DEBUG(SSSDBG_OP_FAILURE, "ldap_parse_result failed (%d)\n",
                                  state->op->msgid);
        ret = EIO;
        goto done;
    }

    DEBUG(SSSDBG_TRACE_LIBS, "ldap_modify result: %s(%d), %s\n",
                              sss_ldap_err2string(result),
                              result, errmsg);

    state->ldap_result = result;
    if (errmsg != NULL) {
        state->ldap_msg = talloc_strdup(state, errmsg);
        if (state->ldap_msg == NULL) {
            ret = ENOMEM;
            goto done;
        }
    }

    ret = EOK;

done:
    ldap_memfree(errmsg);

    if (ret == EOK) {
        tevent_req_done(req);
    } else {
        tevent_req_error(req, ret);
    }
}

static errno_t sdap_modify_recv(TALLOC_CTX *mem_ctx,
                                struct tevent_req *req,
                                int *_ldap_result,
                                char **_ldap_msg)
{
    struct sdap_modify_state *state;

    state = tevent_req_data(req, struct sdap_modify_state);

    TEVENT_REQ_RETURN_ON_ERROR(req);

    if (_ldap_result != NULL) {
        *_ldap_result = state->ldap_result;
    }

    if (_ldap_msg != NULL) {
        *_ldap_msg = talloc_steal(mem_ctx, state->ldap_msg);
    }

    return EOK;
}

struct sdap_modify_passwd_state {
    const char *dn;
    char *user_msg;
};

static void sdap_modify_passwd_done(struct tevent_req *subreq);

struct tevent_req *
sdap_modify_passwd_send(TALLOC_CTX *mem_ctx,
                        struct tevent_context *ev,
                        struct sdap_handle *sh,
                        int timeout,
                        char *attr,
                        const char *user_dn,
                        const char *new_password)
{
    struct tevent_req *req;
    struct tevent_req *subreq;
    struct sdap_modify_passwd_state *state;
    char **values;
    errno_t ret;

    req = tevent_req_create(mem_ctx, &state, struct sdap_modify_passwd_state);
    if (req == NULL) {
        return NULL;
    }

    state->dn = user_dn;

    values = talloc_zero_array(state, char *, 2);
    if (values == NULL) {
        ret = ENOMEM;
        goto done;
    }

    values[0] = talloc_strdup(values, new_password);
    if (values[0] == NULL) {
        ret = ENOMEM;
        goto done;
    }

    subreq = sdap_modify_send(state, ev, sh, timeout, user_dn, attr, values);
    if (subreq == NULL) {
        ret = ENOMEM;
        goto done;
    }

    tevent_req_set_callback(subreq, sdap_modify_passwd_done, req);

    ret = EOK;

done:
    if (ret != EOK) {
        tevent_req_error(req, ret);
        tevent_req_post(req, ev);
    }

    return req;
}

static void sdap_modify_passwd_done(struct tevent_req *subreq)
{
    struct tevent_req *req;
    struct sdap_modify_passwd_state *state;
    int ldap_result;
    char *ldap_msg;
    errno_t ret;

    req = tevent_req_callback_data(subreq, struct tevent_req);
    state = tevent_req_data(req, struct sdap_modify_passwd_state);

    ret = sdap_modify_recv(state, subreq, &ldap_result, &ldap_msg);
    talloc_zfree(subreq);
    if (ret != EOK) {
        DEBUG(SSSDBG_CRIT_FAILURE, "Password change for [%s] failed [%d]: %s\n",
              state->dn, ret, sss_strerror(ret));
        tevent_req_error(req, ret);
        return;
    }

    ret = sdap_chpass_result(state, ldap_result, ldap_msg, &state->user_msg);
    if (ret != EOK) {
        DEBUG(SSSDBG_CRIT_FAILURE, "Password change for [%s] failed [%d]: %s\n",
              state->dn, ret, sss_strerror(ret));
        tevent_req_error(req, ret);
        return;
    }

    DEBUG(SSSDBG_TRACE_FUNC, "Password change for [%s] was successful\n",
          state->dn);

    tevent_req_done(req);
}

errno_t sdap_modify_passwd_recv(struct tevent_req *req,
                                TALLOC_CTX * mem_ctx,
                                char **_user_error_message)
{
    struct sdap_modify_passwd_state *state;

    state = tevent_req_data(req, struct sdap_modify_passwd_state);

    /* We want to return the error message even on failure */
    *_user_error_message = talloc_steal(mem_ctx, state->user_msg);

    TEVENT_REQ_RETURN_ON_ERROR(req);

    return EOK;
}

/* ==Update-passwordLastChanged-attribute====================== */
struct sdap_modify_shadow_lastchange_state {
    const char *dn;
};

static void sdap_modify_shadow_lastchange_done(struct tevent_req *subreq);

struct tevent_req *
sdap_modify_shadow_lastchange_send(TALLOC_CTX *mem_ctx,
                                   struct tevent_context *ev,
                                   struct sdap_handle *sh,
                                   const char *dn,
                                   char *attr)
{
    struct tevent_req *req;
    struct tevent_req *subreq;
    struct sdap_modify_shadow_lastchange_state *state;
    char **values;
    errno_t ret;

    req = tevent_req_create(mem_ctx, &state,
                            struct sdap_modify_shadow_lastchange_state);
    if (req == NULL) {
        return NULL;
    }

    state->dn = dn;
    values = talloc_zero_array(state, char *, 2);
    if (values == NULL) {
        ret = ENOMEM;
        goto done;
    }

    /* The attribute contains number of days since the epoch */
    values[0] = talloc_asprintf(values, "%"SPRItime, time(NULL)/86400);
    if (values[0] == NULL) {
        ret = ENOMEM;
        goto done;
    }

    subreq = sdap_modify_send(state, ev, sh, 5, dn, attr, values);
    if (subreq == NULL) {
        ret = ENOMEM;
        goto done;
    }

    tevent_req_set_callback(subreq, sdap_modify_shadow_lastchange_done, req);

    ret = EOK;

done:
    if (ret != EOK) {
        tevent_req_error(req, ret);
        tevent_req_post(req, ev);
    }
    return req;
}

static void sdap_modify_shadow_lastchange_done(struct tevent_req *subreq)
{
    struct tevent_req *req;
    struct sdap_modify_shadow_lastchange_state *state;
    errno_t ret;

    req = tevent_req_callback_data(subreq, struct tevent_req);
    state = tevent_req_data(req, struct sdap_modify_shadow_lastchange_state);

    ret = sdap_modify_recv(state, subreq, NULL, NULL);
    talloc_zfree(subreq);
    if (ret != EOK) {
        DEBUG(SSSDBG_CRIT_FAILURE, "shadowLastChange change for [%s] failed [%d]: %s\n",
              state->dn, ret, sss_strerror(ret));
        tevent_req_error(req, ret);
        return;
    }

    DEBUG(SSSDBG_TRACE_FUNC, "shadowLastChange change for [%s] was successful\n",
          state->dn);

    tevent_req_done(req);
}

errno_t sdap_modify_shadow_lastchange_recv(struct tevent_req *req)
{
    TEVENT_REQ_RETURN_ON_ERROR(req);

    return EOK;
}


/* ==Fetch-RootDSE============================================= */

struct sdap_get_rootdse_state {
    struct tevent_context *ev;
    struct sdap_options *opts;
    struct sdap_handle *sh;

    struct sysdb_attrs *rootdse;
};

static void sdap_get_rootdse_done(struct tevent_req *subreq);

struct tevent_req *sdap_get_rootdse_send(TALLOC_CTX *memctx,
                                         struct tevent_context *ev,
                                         struct sdap_options *opts,
                                         struct sdap_handle *sh)
{
    struct tevent_req *req, *subreq;
    struct sdap_get_rootdse_state *state;
    const char *attrs[] = {
            "*",
            "altServer",
            SDAP_ROOTDSE_ATTR_NAMING_CONTEXTS,
            "supportedControl",
            "supportedExtension",
            "supportedFeatures",
            "supportedLDAPVersion",
            "supportedSASLMechanisms",
            SDAP_ROOTDSE_ATTR_AD_VERSION,
            SDAP_ROOTDSE_ATTR_DEFAULT_NAMING_CONTEXT,
            SDAP_IPA_LAST_USN, SDAP_AD_LAST_USN,
            NULL
    };

    DEBUG(SSSDBG_TRACE_ALL, "Getting rootdse\n");

    req = tevent_req_create(memctx, &state, struct sdap_get_rootdse_state);
    if (!req) return NULL;

    state->ev = ev;
    state->opts = opts;
    state->sh = sh;
    state->rootdse = NULL;

    subreq = sdap_get_generic_send(state, ev, opts, sh,
                                   "", LDAP_SCOPE_BASE,
                                   "(objectclass=*)", attrs, NULL, 0,
                                   dp_opt_get_int(state->opts->basic,
                                                  SDAP_SEARCH_TIMEOUT),
                                   false);
    if (!subreq) {
        talloc_zfree(req);
        return NULL;
    }
    tevent_req_set_callback(subreq, sdap_get_rootdse_done, req);

    return req;
}

/* This is not a real attribute, it's just there to avoid
 * actually pulling real data down, to save bandwidth
 */
static void sdap_get_rootdse_done(struct tevent_req *subreq)
{
    struct tevent_req *req = tevent_req_callback_data(subreq,
                                                      struct tevent_req);
    struct sdap_get_rootdse_state *state = tevent_req_data(req,
                                             struct sdap_get_rootdse_state);
    struct sysdb_attrs **results;
    size_t num_results;
    int ret;

    ret = sdap_get_generic_recv(subreq, state, &num_results, &results);
    talloc_zfree(subreq);
    if (ret) {
        tevent_req_error(req, ret);
        return;
    }

    if (num_results == 0 || !results) {
        DEBUG(SSSDBG_OP_FAILURE, "RootDSE could not be retrieved. "
                  "Please check that anonymous access to RootDSE is allowed\n"
              );
        tevent_req_error(req, ENOENT);
        return;
    }

    if (num_results > 1) {
        DEBUG(SSSDBG_OP_FAILURE,
              "Multiple replies when searching for RootDSE??\n");
        tevent_req_error(req, EIO);
        return;
    }

    state->rootdse = talloc_steal(state, results[0]);
    talloc_zfree(results);

    DEBUG(SSSDBG_TRACE_INTERNAL, "Got rootdse\n");

    tevent_req_done(req);
    return;
}

int sdap_get_rootdse_recv(struct tevent_req *req,
                          TALLOC_CTX *memctx,
                          struct sysdb_attrs **rootdse)
{
    struct sdap_get_rootdse_state *state = tevent_req_data(req,
                                             struct sdap_get_rootdse_state);

    TEVENT_REQ_RETURN_ON_ERROR(req);

    *rootdse = talloc_steal(memctx, state->rootdse);

    return EOK;
}

/* ==Helpers for parsing replies============================== */
struct sdap_reply {
    size_t reply_max;
    size_t reply_count;
    struct sysdb_attrs **reply;
};

struct sdap_reply_with_type {
    size_t reply_max;
    size_t reply_count;
    struct sysdb_attrs **reply;
    int *reply_type; /* Optional indicator of the type of the corresponding
                      * reply */
};

static inline size_t increase_reply_max(size_t current)
{
    return current == 0 ? REPLY_INITIAL_SIZE : (current * 2);
}

static errno_t add_to_reply(TALLOC_CTX *mem_ctx,
                            struct sdap_reply *sreply,
                            struct sysdb_attrs *msg)
{
    if (sreply->reply == NULL || sreply->reply_max == sreply->reply_count) {
        sreply->reply_max = increase_reply_max(sreply->reply_max);
        sreply->reply = talloc_realloc(mem_ctx, sreply->reply,
                                       struct sysdb_attrs *,
                                       sreply->reply_max);
        if (sreply->reply == NULL) {
            DEBUG(SSSDBG_CRIT_FAILURE, "talloc_realloc failed.\n");
            return ENOMEM;
        }
    }

    sreply->reply[sreply->reply_count++] = talloc_steal(sreply->reply, msg);

    return EOK;
}

static errno_t add_to_reply_with_type(TALLOC_CTX *mem_ctx,
                                      struct sdap_reply_with_type *sreply,
                                      struct sysdb_attrs *msg,
                                      int type)
{
    if (sreply->reply == NULL || sreply->reply_max == sreply->reply_count) {
        sreply->reply_max = increase_reply_max(sreply->reply_max);
        sreply->reply = talloc_realloc(mem_ctx, sreply->reply,
                                       struct sysdb_attrs *,
                                       sreply->reply_max);
        if (sreply->reply == NULL) {
            DEBUG(SSSDBG_CRIT_FAILURE, "talloc_realloc failed.\n");
            return ENOMEM;
        }

        sreply->reply_type = talloc_realloc(mem_ctx, sreply->reply_type,
                                            int,
                                            sreply->reply_max);
        if (sreply->reply_type == NULL) {
            DEBUG(SSSDBG_CRIT_FAILURE, "talloc_realloc failed.\n");
            return ENOMEM;
        }
    }

    sreply->reply_type[sreply->reply_count] = type;
    sreply->reply[sreply->reply_count++] = talloc_steal(sreply->reply, msg);

    return EOK;
}


struct sdap_deref_reply {
    size_t reply_max;
    size_t reply_count;
    struct sdap_deref_attrs **reply;
};

static errno_t add_to_deref_reply(TALLOC_CTX *mem_ctx,
                                  int num_maps,
                                  struct sdap_deref_reply *dreply,
                                  struct sdap_deref_attrs **res)
{
    int i;

    if (res == NULL) {
        /* Nothing to add, probably ACIs prevented us from dereferencing
         * the attribute */
        return EOK;
    }

    for (i=0; i < num_maps; i++) {
        if (res[i]->attrs == NULL) continue; /* Nothing in this map */

        if (dreply->reply == NULL ||
            dreply->reply_max == dreply->reply_count) {
            dreply->reply_max = increase_reply_max(dreply->reply_max);
            dreply->reply = talloc_realloc(mem_ctx, dreply->reply,
                                        struct sdap_deref_attrs *,
                                        dreply->reply_max);
            if (dreply->reply == NULL) {
                DEBUG(SSSDBG_CRIT_FAILURE, "talloc_realloc failed.\n");
                return ENOMEM;
            }
        }

        dreply->reply[dreply->reply_count++] =
            talloc_steal(dreply->reply, res[i]);
    }

    return EOK;
}

const char *sdap_get_server_peer_str(struct sdap_handle *sh)
{
    int ret;
    int fd;
    struct sockaddr sa;
    socklen_t sa_len = sizeof(sa);
    char ip[NI_MAXHOST];
    static char out[NI_MAXHOST + 8];
    int port;

    ret = get_fd_from_ldap(sh->ldap, &fd);
    if (ret != EOK) {
        DEBUG(SSSDBG_MINOR_FAILURE, "cannot get sdap fd\n");
        return NULL;
    }

    ret = getpeername(fd, &sa, &sa_len);
    if (ret == -1) {
        DEBUG(SSSDBG_MINOR_FAILURE, "getpeername failed\n");
        return NULL;
    }

    switch (sa.sa_family) {
    case AF_INET: {
        struct sockaddr_in in;
        socklen_t in_len = sizeof(in);

        ret = getpeername(fd, (struct sockaddr *)(&in), &in_len);
        if (ret == -1) {
            DEBUG(SSSDBG_MINOR_FAILURE, "getpeername failed\n");
            return NULL;
        }

        ret = getnameinfo((struct sockaddr *)(&in), in_len,
                          ip, sizeof(ip), NULL, 0, NI_NUMERICHOST);
        if (ret != 0) {
            DEBUG(SSSDBG_MINOR_FAILURE, "getnameinfo failed\n");
            return NULL;
        }

        port = ntohs(in.sin_port);
        ret = snprintf(out, sizeof(out), "%s:%d", ip, port);
        break;
    }
    case AF_INET6: {
        struct sockaddr_in6 in6;
        socklen_t in6_len = sizeof(in6);

        ret = getpeername(fd, (struct sockaddr *)(&in6), &in6_len);
        if (ret == -1) {
            DEBUG(SSSDBG_MINOR_FAILURE, "getpeername failed\n");
            return NULL;
        }

        ret = getnameinfo((struct sockaddr *)(&in6), in6_len,
                          ip, sizeof(ip), NULL, 0, NI_NUMERICHOST);
        if (ret != 0) {
            DEBUG(SSSDBG_MINOR_FAILURE, "getnameinfo failed\n");
            return NULL;
        }

        port = ntohs(in6.sin6_port);
        ret = snprintf(out, sizeof(out), "[%s]:%d", ip, port);
        break;
    }
    case AF_UNIX: {
        struct sockaddr_un un;
        socklen_t un_len = sizeof(un);

        ret = getpeername(fd, (struct sockaddr *)(&un), &un_len);
        if (ret == -1) {
            DEBUG(SSSDBG_MINOR_FAILURE, "getpeername failed\n");
            return NULL;
        }

        ret = snprintf(out, sizeof(out), "%.*s",
            (int)strnlen(un.sun_path, un_len - offsetof(struct sockaddr_un,
                sun_path)), un.sun_path);
        break;
    }
    default:
        return NULL;
    }

    if (ret < 0 || ret >= sizeof(out)) {
        return NULL;
    }

    return out;
}

const char *sdap_get_server_peer_str_safe(struct sdap_handle *sh)
{
    const char *ip = sdap_get_server_peer_str(sh);
    return ip != NULL ? ip : "- IP not available -";
}

static void sdap_print_server(struct sdap_handle *sh)
{
    const char *ip;

    /* The purpose of the call is to add the server IP to the debug output if
     * debug_level is SSSDBG_TRACE_INTERNAL or higher */
    if (DEBUG_IS_SET(SSSDBG_TRACE_INTERNAL)) {
        ip = sdap_get_server_peer_str(sh);
        if (ip != NULL) {
            DEBUG(SSSDBG_TRACE_INTERNAL, "Searching %s\n", ip);
        } else {
            DEBUG(SSSDBG_OP_FAILURE, "sdap_get_server_peer_str failed.\n");
        }
    }
}

/* ==Generic Search exposing all options======================= */
typedef errno_t (*sdap_parse_cb)(struct sdap_handle *sh,
                                 struct sdap_msg *msg,
                                 void *pvt);

struct sdap_get_generic_ext_state {
    struct tevent_context *ev;
    struct sdap_options *opts;
    struct sdap_handle *sh;
    const char *search_base;
    int scope;
    const char *filter;
    const char **attrs;
    int timeout;
    int sizelimit;

    struct sdap_op *op;

    struct berval cookie;

    LDAPControl **serverctrls;
    int nserverctrls;
    LDAPControl **clientctrls;

    size_t ref_count;
    char **refs;

    sdap_parse_cb parse_cb;
    void *cb_data;

    unsigned int flags;
};

static errno_t sdap_get_generic_ext_step(struct tevent_req *req);

static void sdap_get_generic_op_finished(struct sdap_op *op,
                                         struct sdap_msg *reply,
                                         int error, void *pvt);

enum {
    /* Be silent about exceeded size limit */
    SDAP_SRCH_FLG_SIZELIMIT_SILENT = 1 << 0,

    /* Allow paging */
    SDAP_SRCH_FLG_PAGING           = 1 << 1,

    /* Only attribute descriptions are requested */
    SDAP_SRCH_FLG_ATTRS_ONLY       = 1 << 2,
};

static struct tevent_req *
sdap_get_generic_ext_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,
                          LDAPControl **serverctrls,
                          LDAPControl **clientctrls,
                          int sizelimit,
                          int timeout,
                          sdap_parse_cb parse_cb,
                          void *cb_data,
                          unsigned int flags)
{
    errno_t ret;
    struct sdap_get_generic_ext_state *state;
    struct tevent_req *req;
    int i;
    LDAPControl *control;

    req = tevent_req_create(memctx, &state, struct sdap_get_generic_ext_state);
    if (!req) return NULL;

    state->ev = ev;
    state->opts = opts;
    state->sh = sh;
    state->search_base = search_base;
    state->scope = scope;
    state->filter = filter;
    state->attrs = attrs;
    state->op = NULL;
    state->sizelimit = sizelimit;
    state->timeout = timeout;
    state->cookie.bv_len = 0;
    state->cookie.bv_val = NULL;
    state->parse_cb = parse_cb;
    state->cb_data = cb_data;
    state->clientctrls = clientctrls;
    state->flags = flags;

    if (state->sh == NULL || state->sh->ldap == NULL) {
        DEBUG(SSSDBG_CRIT_FAILURE,
              "Trying LDAP search while not connected.\n");
        tevent_req_error(req, EIO);
        tevent_req_post(req, ev);
        return req;
    }

    sdap_print_server(sh);

    /* Be extra careful and never allow paging for BASE searches,
     * even if requested.
     */
    if (scope == LDAP_SCOPE_BASE && (flags & SDAP_SRCH_FLG_PAGING)) {
        /* Disable paging */
        state->flags &= ~SDAP_SRCH_FLG_PAGING;
        DEBUG(SSSDBG_TRACE_FUNC,
              "WARNING: Disabling paging because scope is set to base.\n");
    }

    /* Also check for deref/asq requests and force
     * paging on for those requests
     */
    /* X-DEREF */
    control = ldap_control_find(LDAP_CONTROL_X_DEREF,
                                serverctrls,
                                NULL);
    if (control) {
        state->flags |= SDAP_SRCH_FLG_PAGING;
    }

    /* ASQ */
    control = ldap_control_find(LDAP_SERVER_ASQ_OID,
                                serverctrls,
                                NULL);
    if (control) {
        state->flags |= SDAP_SRCH_FLG_PAGING;
    }

    for (state->nserverctrls=0;
         serverctrls && serverctrls[state->nserverctrls];
         state->nserverctrls++) ;

    /* One extra space for NULL, one for page control */
    state->serverctrls = talloc_array(state, LDAPControl *,
                                      state->nserverctrls+2);
    if (!state->serverctrls) {
        tevent_req_error(req, ENOMEM);
        tevent_req_post(req, ev);
        return req;
    }

    for (i=0; i < state->nserverctrls; i++) {
        state->serverctrls[i] = serverctrls[i];
    }
    state->serverctrls[i] = NULL;

    PROBE(SDAP_GET_GENERIC_EXT_SEND, state->search_base,
          state->scope, state->filter, state->attrs);

    ret = sdap_get_generic_ext_step(req);
    if (ret != EOK) {
        tevent_req_error(req, ret);
        tevent_req_post(req, ev);
        return req;
    }

    return req;
}

static errno_t sdap_get_generic_ext_step(struct tevent_req *req)
{
    struct sdap_get_generic_ext_state *state =
            tevent_req_data(req, struct sdap_get_generic_ext_state);
    int lret;
    errno_t ret;
    int msgid;
    bool disable_paging;
    char *stat_info;

    LDAPControl *page_control = NULL;

    /* Make sure to free any previous operations so
     * if we are handling a large number of pages we
     * don't waste memory.
     */
    talloc_zfree(state->op);

    disable_paging = dp_opt_get_bool(state->opts->basic, SDAP_DISABLE_PAGING);

    if (!disable_paging
            && (state->flags & SDAP_SRCH_FLG_PAGING)
            && sdap_is_control_supported(state->sh,
                                         LDAP_CONTROL_PAGEDRESULTS)) {
        lret = ldap_create_page_control(state->sh->ldap,
                                        state->sh->page_size,
                                        state->cookie.bv_val ?
                                            &state->cookie :
                                            NULL,
                                        false,
                                        &page_control);
        if (lret != LDAP_SUCCESS) {
            ret = EIO;
            goto done;
        }
        state->serverctrls[state->nserverctrls] = page_control;
        state->serverctrls[state->nserverctrls+1] = NULL;
    }

    lret = ldap_search_ext(state->sh->ldap, state->search_base,
                           state->scope, state->filter,
                           discard_const(state->attrs),
                           (state->flags & SDAP_SRCH_FLG_ATTRS_ONLY),
                           state->serverctrls,
                           state->clientctrls, NULL, state->sizelimit, &msgid);
    ldap_control_free(page_control);
    state->serverctrls[state->nserverctrls] = NULL;
    if (lret != LDAP_SUCCESS) {
        DEBUG(SSSDBG_MINOR_FAILURE,
              "ldap_search_ext failed: %s\n", sss_ldap_err2string(lret));
        if (lret == LDAP_SERVER_DOWN) {
            ret = ETIMEDOUT;
            sss_ldap_error_debug(SSSDBG_MINOR_FAILURE, "Connection error",
                                 state->sh->ldap, lret);
            sss_log(SSS_LOG_ERR, "LDAP connection error");
        } else if (lret == LDAP_FILTER_ERROR) {
            ret = ERR_INVALID_FILTER;
        } else {
            ret = EIO;
        }
        goto done;
    }

    stat_info = talloc_asprintf(state, "server: [%s] filter: [%s] base: [%s]",
                                sdap_get_server_peer_str_safe(state->sh),
                                state->filter, state->search_base);
    if (stat_info == NULL) {
        DEBUG(SSSDBG_OP_FAILURE, "Failed to create info string, ignored.\n");
    }

    DEBUG(SSSDBG_TRACE_FUNC, "ldap_search_ext called: %s; msgid = %d\n",
          (stat_info ? stat_info : "N/A"), msgid);
    if (state->attrs) {
        for (int i = 0; state->attrs[i]; i++) {
            DEBUG_CONDITIONAL(SSSDBG_TRACE_ALL, "Requesting attrs: [%s]\n",
                              state->attrs[i]);
        }
    }

    ret = sdap_op_add(state, state->ev, state->sh, msgid, stat_info,
                      sdap_get_generic_op_finished, req,
                      state->timeout,
                      &state->op);
    if (ret != EOK) {
        DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set up operation!\n");
        goto done;
    }

done:
    return ret;
}

static errno_t
sdap_get_generic_ext_add_references(struct sdap_get_generic_ext_state *state,
                                    char **refs)
{
    int i;

    if (refs == NULL) {
        /* Rare, but it's possible that we might get a reference result with
         * no references attached.
         */
        return EOK;
    }

    for (i = 0; refs[i]; i++) {
        DEBUG(SSSDBG_TRACE_LIBS, "Additional References: %s\n", refs[i]);
    }

    /* Extend the size of the ref array */
    state->refs = talloc_realloc(state, state->refs, char *,
                                 state->ref_count + i);
    if (state->refs == NULL) {
        DEBUG(SSSDBG_CRIT_FAILURE,
              "talloc_realloc failed extending ref_array.\n");
        return ENOMEM;
    }

    /* Copy in all the references */
    for (i = 0; refs[i]; i++) {
        state->refs[state->ref_count + i] =
                talloc_strdup(state->refs, refs[i]);

        if (state->refs[state->ref_count + i] == NULL) {
            return ENOMEM;
        }
    }

    state->ref_count += i;

    return EOK;
}

static void sdap_get_generic_op_finished(struct sdap_op *op,
                                         struct sdap_msg *reply,
                                         int error, void *pvt)
{
    struct tevent_req *req = talloc_get_type(pvt, struct tevent_req);
    struct sdap_get_generic_ext_state *state = tevent_req_data(req,
                                            struct sdap_get_generic_ext_state);
    char *errmsg = NULL;
    char **refs = NULL;
    int result;
    int ret;
    int lret;
    ber_int_t total_count;
    struct berval cookie;
    LDAPControl **returned_controls = NULL;
    LDAPControl *page_control;

    if (error) {
        tevent_req_error(req, error);
        return;
    }

    switch (ldap_msgtype(reply->msg)) {
    case LDAP_RES_SEARCH_REFERENCE:
        ret = ldap_parse_reference(state->sh->ldap, reply->msg,
                                   &refs, NULL, 0);
        if (ret != LDAP_SUCCESS) {
            DEBUG(SSSDBG_OP_FAILURE,
                  "ldap_parse_reference failed (%d)\n", state->op->msgid);
            tevent_req_error(req, EIO);
            return;
        }

        ret = sdap_get_generic_ext_add_references(state, refs);
        if (ret != EOK) {
            DEBUG(SSSDBG_OP_FAILURE,
                  "sdap_get_generic_ext_add_references failed: %s(%d)\n",
                  sss_strerror(ret), ret);
            ldap_memvfree((void **)refs);
            tevent_req_error(req, ret);
            return;
        }

        /* Remove the original strings */
        ldap_memvfree((void **)refs);

        /* unlock the operation so that we can proceed with the next result */
        sdap_unlock_next_reply(state->op);
        break;

    case LDAP_RES_SEARCH_ENTRY:
        ret = state->parse_cb(state->sh, reply, state->cb_data);
        if (ret != EOK) {
            DEBUG(SSSDBG_CRIT_FAILURE, "reply parsing callback failed.\n");
            tevent_req_error(req, ret);
            return;
        }

        sdap_unlock_next_reply(state->op);
        break;

    case LDAP_RES_SEARCH_RESULT:
        ret = ldap_parse_result(state->sh->ldap, reply->msg,
                                &result, NULL, &errmsg, &refs,
                                &returned_controls, 0);
        if (ret != LDAP_SUCCESS) {
            DEBUG(SSSDBG_OP_FAILURE,
                  "ldap_parse_result failed (%d)\n", state->op->msgid);
            tevent_req_error(req, EIO);
            return;
        }

        DEBUG(SSSDBG_TRACE_FUNC, "Search result: %s(%d), %s\n",
                  sss_ldap_err2string(result), result,
                  errmsg ? errmsg : "no errmsg set");

        if (result == LDAP_SIZELIMIT_EXCEEDED
                || result == LDAP_ADMINLIMIT_EXCEEDED) {
            /* Try to return what we've got */

            if ( ! (state->flags & SDAP_SRCH_FLG_SIZELIMIT_SILENT)) {
                DEBUG(SSSDBG_MINOR_FAILURE,
                      "LDAP sizelimit was exceeded, "
                      "returning incomplete data\n");
            }
        } else if (result == LDAP_INAPPROPRIATE_MATCHING) {
            /* This error should only occur when we're testing for
             * specialized functionality like the LDAP matching rule
             * filter for Active Directory. Warn at a higher log
             * level and return EIO.
             */
            DEBUG(SSSDBG_TRACE_INTERNAL,
                  "LDAP_INAPPROPRIATE_MATCHING:  %s\n",
                   errmsg ? errmsg : "no errmsg set");
            ldap_memfree(errmsg);
            tevent_req_error(req, EIO);
            return;
        } else if (result == LDAP_UNAVAILABLE_CRITICAL_EXTENSION) {
            ldap_memfree(errmsg);
            tevent_req_error(req, ENOTSUP);
            return;
        } else if (result == LDAP_REFERRAL) {
            ret = sdap_get_generic_ext_add_references(state, refs);
            if (ret != EOK) {
                DEBUG(SSSDBG_OP_FAILURE,
                      "sdap_get_generic_ext_add_references failed: %s(%d)\n",
                      sss_strerror(ret), ret);
                tevent_req_error(req, ret);
            }
            /* For referrals, we need to fall through as if it was LDAP_SUCCESS */
        } else if (result != LDAP_SUCCESS && result != LDAP_NO_SUCH_OBJECT) {
            DEBUG(SSSDBG_OP_FAILURE,
                  "Unexpected result from ldap: %s(%d), %s\n",
                   sss_ldap_err2string(result), result,
                   errmsg ? errmsg : "no errmsg set");
            ldap_memfree(errmsg);
            tevent_req_error(req, EIO);
            return;
        }
        ldap_memfree(errmsg);

        /* Determine if there are more pages to retrieve */
        page_control = ldap_control_find(LDAP_CONTROL_PAGEDRESULTS,
                                         returned_controls, NULL );
        if (!page_control) {
            /* No paging support. We are done */
            tevent_req_done(req);
            return;
        }

        lret = ldap_parse_pageresponse_control(state->sh->ldap, page_control,
                                               &total_count, &cookie);
        ldap_controls_free(returned_controls);
        if (lret != LDAP_SUCCESS) {
            DEBUG(SSSDBG_CRIT_FAILURE, "Could not determine page control\n");
            tevent_req_error(req, EIO);
            return;
        }
        DEBUG(SSSDBG_TRACE_INTERNAL, "Total count [%d]\n", total_count);

        if (cookie.bv_val != NULL && cookie.bv_len > 0) {
            /* Cookie contains data, which means there are more requests
             * to be processed.
             */
            talloc_zfree(state->cookie.bv_val);
            state->cookie.bv_len = cookie.bv_len;
            state->cookie.bv_val = talloc_memdup(state,
                                                 cookie.bv_val,
                                                 cookie.bv_len);
            if (!state->cookie.bv_val) {
                tevent_req_error(req, ENOMEM);
                return;
            }
            ber_memfree(cookie.bv_val);

            ret = sdap_get_generic_ext_step(req);
            if (ret != EOK) {
                tevent_req_error(req, ENOMEM);
                return;
            }

            return;
        }
        /* The cookie must be freed even if len == 0 */
        ber_memfree(cookie.bv_val);

        /* This was the last page. We're done */

        tevent_req_done(req);
        return;

    default:
        /* what is going on here !? */
        tevent_req_error(req, EIO);
        return;
    }
}

static int
sdap_get_generic_ext_recv(struct tevent_req *req,
                          TALLOC_CTX *mem_ctx,
                          size_t *ref_count,
                          char ***refs)
{
    struct sdap_get_generic_ext_state *state =
            tevent_req_data(req, struct sdap_get_generic_ext_state);

    PROBE(SDAP_GET_GENERIC_EXT_RECV, state->search_base,
          state->scope, state->filter);

    TEVENT_REQ_RETURN_ON_ERROR(req);

    if (ref_count) {
        *ref_count = state->ref_count;
    }

    if (refs) {
        *refs = talloc_steal(mem_ctx, state->refs);
    }

    return EOK;
}

/* This search handler can be used by most calls */
static void generic_ext_search_handler(struct tevent_req *subreq,
                                       struct sdap_options *opts)
{
    struct tevent_req *req = tevent_req_callback_data(subreq,
                                                      struct tevent_req);
    int ret;
    size_t ref_count, i;
    char **refs;

    ret = sdap_get_generic_ext_recv(subreq, req, &ref_count, &refs);
    talloc_zfree(subreq);

    if (ret != EOK) {
        if (ret == ETIMEDOUT) {
            DEBUG(SSSDBG_CRIT_FAILURE,
                  "sdap_get_generic_ext_recv failed: [%d]: %s "
                  "[ldap_search_timeout]\n",
                  ret, sss_strerror(ret));
        } else {
            DEBUG(SSSDBG_CRIT_FAILURE,
                  "sdap_get_generic_ext_recv request failed: [%d]: %s\n",
                  ret, sss_strerror(ret));
        }
        tevent_req_error(req, ret);
        return;
    }

    if (ref_count > 0) {
        /* We will ignore referrals in the generic handler */
        DEBUG(SSSDBG_TRACE_ALL,
              "Request included referrals which were ignored.\n");
        if (debug_level & SSSDBG_TRACE_ALL) {
            for(i = 0; i < ref_count; i++) {
                DEBUG(SSSDBG_TRACE_ALL,
                      "    Ref: %s\n", refs[i]);
            }
        }
    }

    talloc_free(refs);
    tevent_req_done(req);
}

/* ==Generic Search exposing all options with multiple maps === */
struct sdap_get_and_multi_parse_generic_state {
    struct sdap_attr_map_info_ex *maps;
    size_t num_maps;
    int unmatched_map_type;

    struct sdap_reply_with_type sreply;
    struct sdap_options *opts;
};

static void sdap_get_and_multi_parse_generic_done(struct tevent_req *subreq);
static errno_t
sdap_get_and_multi_parse_generic_parse_entry(struct sdap_handle *sh,
                                             struct sdap_msg *msg,
                                             void *pvt);

struct tevent_req *
sdap_get_and_multi_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_info_ex *maps,
                                      size_t num_maps,
                                      int unmatched_map_type,
                                      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_multi_parse_generic_state *state = NULL;
    unsigned int flags = 0;

    req = tevent_req_create(memctx, &state,
                            struct sdap_get_and_multi_parse_generic_state);
    if (!req) return NULL;

    state->maps = maps;
    state->num_maps = num_maps;
    state->unmatched_map_type = unmatched_map_type;
    state->opts = opts;

    if (allow_paging) {
        flags |= SDAP_SRCH_FLG_PAGING;
    }

    if (attrsonly) {
        flags |= SDAP_SRCH_FLG_ATTRS_ONLY;
    }

    subreq = sdap_get_generic_ext_send(state, ev, opts, sh, search_base,
                                       scope, filter, attrs, serverctrls,
                                       clientctrls, sizelimit, timeout,
                                       sdap_get_and_multi_parse_generic_parse_entry,
                                       state, flags);
    if (!subreq) {
        talloc_zfree(req);
        return NULL;
    }
    tevent_req_set_callback(subreq, sdap_get_and_multi_parse_generic_done, req);

    return req;
}

static bool has_required_attrs(struct sdap_handle *sh, struct sdap_msg *msg,
                               const char **required_attrs)
{
    size_t c;
    struct berval **req_attr;

    for (c = 0; required_attrs[c] != NULL; c++) {
        req_attr = ldap_get_values_len(sh->ldap, msg->msg, required_attrs[c]);
        if (req_attr == NULL) {
            return false;
        }
        ldap_value_free_len(req_attr);
    }

    return true;
}

static errno_t
sdap_get_and_multi_parse_generic_parse_entry(struct sdap_handle *sh,
                                             struct sdap_msg *msg,
                                             void *pvt)
{
    errno_t ret;
    struct sdap_get_and_multi_parse_generic_state *state =
                talloc_get_type(pvt, struct sdap_get_and_multi_parse_generic_state);
    struct berval **vals = NULL;
    int i, mi;
    struct sdap_attr_map *map;
    int num_attrs = 0;
    struct sysdb_attrs *attrs = NULL;
    char *tmp;
    char *dn = NULL;
    TALLOC_CTX *tmp_ctx;
    bool disable_range_rtrvl;
    int type = state->unmatched_map_type;

    tmp_ctx = talloc_new(NULL);
    if (!tmp_ctx) return ENOMEM;

    tmp = ldap_get_dn(sh->ldap, msg->msg);
    if (!tmp) {
        ret = EINVAL;
        goto done;
    }

    dn = talloc_strdup(tmp_ctx, tmp);
    ldap_memfree(tmp);
    if (!dn) {
        ret = ENOMEM;
        goto done;
    }

    /* Find the first matching map in the list */
    vals = ldap_get_values_len(sh->ldap, msg->msg, "objectClass");
    if (!vals) {
        DEBUG(SSSDBG_OP_FAILURE,
              "Unknown entry type, no objectClass found for DN [%s]!\n", dn);
        ret = EINVAL;
        goto done;
    }
    for (mi = 0; mi < state->num_maps; mi++) {
        map = NULL;
        for (i = 0; vals[i]; i++) {
            /* the objectclass is always the first name in the map */
            if (strlen(state->maps[mi].map[0].name) == vals[i]->bv_len
                    && strncasecmp(state->maps[mi].map[0].name,
                                   vals[i]->bv_val, vals[i]->bv_len) == 0) {
                /* it's an entry of the right type */
                DEBUG(SSSDBG_TRACE_INTERNAL,
                      "Matched objectclass [%s] on DN [%s], will use "
                      "associated map\n",
                       state->maps[mi].map[0].name, dn);


                map = state->maps[mi].map;
                num_attrs = state->maps[mi].num_attrs;
                /* Check if all required attributes are present, otherwise the
                 * object should be ignored, e.g. user without POSIX ID. */
                if (has_required_attrs(sh, msg,
                                       state->maps[mi].required_attrs)) {
                    type = state->maps[mi].map_type;
                } else {
                    DEBUG(SSSDBG_TRACE_INTERNAL,
                          "Required attributes missing, treating "
                          "DN [%s] as ignored.\n", dn);
                    type = SDAP_NESTED_GROUP_DN_IGNORE;
                }
                break;
            }
        }
        if (map != NULL) {
            break;
        }

        DEBUG(SSSDBG_TRACE_INTERNAL,
              "DN [%s] did not match the objectClass [%s]\n",
               dn, state->maps[mi].map[0].name);
    }

    if (map != NULL) {
        disable_range_rtrvl = dp_opt_get_bool(state->opts->basic,
                                              SDAP_DISABLE_RANGE_RETRIEVAL);

        ret = sdap_parse_entry(state, sh, msg,
                               map, num_attrs,
                               &attrs, disable_range_rtrvl);
        if (ret != EOK) {
            DEBUG(SSSDBG_MINOR_FAILURE,
                  "sdap_parse_entry failed [%d]: %s\n", ret, strerror(ret));
            goto done;
        }
        ret = sysdb_attrs_add_string(attrs, SYSDB_OBJECTCLASS, map[0].name);
        if (ret != EOK) {
            DEBUG(SSSDBG_OP_FAILURE, "Failed to add objectclass.\n");
            goto done;
        }
    }

    /* If some mapped entry was found, add it to the reply */
    if (attrs != NULL) {
        ret = add_to_reply_with_type(state, &state->sreply, attrs, type);
        if (ret != EOK) {
            DEBUG(SSSDBG_CRIT_FAILURE, "add_to_reply failed.\n");
            goto done;
        }
    }

    ret = EOK;
done:
    ldap_value_free_len(vals);
    talloc_zfree(tmp_ctx);
    return ret;
}

static void sdap_get_and_multi_parse_generic_done(struct tevent_req *subreq)
{
    struct tevent_req *req = tevent_req_callback_data(subreq,
                                                      struct tevent_req);
    struct sdap_get_and_multi_parse_generic_state *state = tevent_req_data(req,
                                 struct sdap_get_and_multi_parse_generic_state);

    return generic_ext_search_handler(subreq, state->opts);
}

int sdap_get_and_multi_parse_generic_recv(struct tevent_req *req,
                                          TALLOC_CTX *mem_ctx,
                                          size_t *reply_count,
                                          struct sysdb_attrs ***reply,
                                          int **reply_type)
{
    struct sdap_get_and_multi_parse_generic_state *state = tevent_req_data(req,
                                 struct sdap_get_and_multi_parse_generic_state);

    TEVENT_REQ_RETURN_ON_ERROR(req);

    *reply_count = state->sreply.reply_count;
    *reply = talloc_steal(mem_ctx, state->sreply.reply);
    *reply_type = talloc_steal(mem_ctx, state->sreply.reply_type);

    return EOK;
}

/* ==Generic Search exposing all options======================= */
struct sdap_get_and_parse_generic_state {
    struct sdap_attr_map *map;
    int map_num_attrs;

    struct sdap_reply sreply;
    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;
    unsigned int flags = 0;

    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;

    if (allow_paging) {
        flags |= SDAP_SRCH_FLG_PAGING;
    }

    if (attrsonly) {
        flags |= SDAP_SRCH_FLG_ATTRS_ONLY;
    }

    subreq = sdap_get_generic_ext_send(state, ev, opts, sh, search_base,
                                       scope, filter, attrs, serverctrls,
                                       clientctrls, sizelimit, timeout,
                                       sdap_get_and_parse_generic_parse_entry,
                                       state, flags);
    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);

struct tevent_req *sdap_get_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 timeout,
                                         bool allow_paging)
{
    struct tevent_req *req = NULL;
    struct tevent_req *subreq = NULL;
    struct sdap_get_generic_state *state = NULL;

    req = tevent_req_create(memctx, &state, struct sdap_get_generic_state);
    if (!req) return NULL;

    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) {
        talloc_zfree(req);
        return NULL;
    }
    tevent_req_set_callback(subreq, sdap_get_generic_done, req);

    return req;
}

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;

    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,
                          TALLOC_CTX *mem_ctx,
                          size_t *reply_count,
                          struct sysdb_attrs ***reply)
{
    struct sdap_get_generic_state *state =
                tevent_req_data(req, struct sdap_get_generic_state);

    TEVENT_REQ_RETURN_ON_ERROR(req);

    *reply_count = state->reply_count;
    *reply = talloc_steal(mem_ctx, state->reply);

    return EOK;
}

/* ==OpenLDAP deref search============================================== */
static int sdap_x_deref_create_control(struct sdap_handle *sh,
                                       const char *deref_attr,
                                       const char **attrs,
                                       LDAPControl **ctrl);

static void sdap_x_deref_search_done(struct tevent_req *subreq);
static int sdap_x_deref_search_ctrls_destructor(void *ptr);

static errno_t sdap_x_deref_parse_entry(struct sdap_handle *sh,
                                        struct sdap_msg *msg,
                                        void *pvt);
struct sdap_x_deref_search_state {
    struct sdap_handle *sh;
    struct sdap_op *op;
    struct sdap_attr_map_info *maps;
    LDAPControl **ctrls;
    struct sdap_options *opts;
    bool ldap_ignore_unreadable_references;

    struct sdap_deref_reply dreply;
    int num_maps;
};

static struct tevent_req *
sdap_x_deref_search_send(TALLOC_CTX *memctx, struct tevent_context *ev,
                         struct sdap_options *opts, struct sdap_handle *sh,
                         const char *base_dn, const char *filter,
                         const char *deref_attr, const char **attrs,
                         struct sdap_attr_map_info *maps, int num_maps,
                         int timeout)
{
    struct tevent_req *req = NULL;
    struct tevent_req *subreq = NULL;
    struct sdap_x_deref_search_state *state;
    int ret;

    req = tevent_req_create(memctx, &state, struct sdap_x_deref_search_state);
    if (!req) return NULL;

    state->sh = sh;
    state->maps = maps;
    state->op = NULL;
    state->opts = opts;
    state->num_maps = num_maps;
    state->ctrls = talloc_zero_array(state, LDAPControl *, 2);
    if (state->ctrls == NULL) {
        talloc_zfree(req);
        return NULL;
    }
    talloc_set_destructor((TALLOC_CTX *) state->ctrls,
                          sdap_x_deref_search_ctrls_destructor);

    state->ldap_ignore_unreadable_references = dp_opt_get_bool(opts->basic,
                                            SDAP_IGNORE_UNREADABLE_REFERENCES);

    ret = sdap_x_deref_create_control(sh, deref_attr,
                                      attrs, &state->ctrls[0]);
    if (ret != EOK) {
        DEBUG(SSSDBG_CRIT_FAILURE, "Could not create OpenLDAP deref control\n");
        talloc_zfree(req);
        return NULL;
    }

    DEBUG(SSSDBG_TRACE_FUNC,
          "Dereferencing entry [%s] using OpenLDAP deref\n", base_dn);
    subreq = sdap_get_generic_ext_send(state, ev, opts, sh, base_dn,
                                       filter == NULL ? LDAP_SCOPE_BASE
                                                      : LDAP_SCOPE_SUBTREE,
                                       filter, attrs,
                                       state->ctrls, NULL, 0, timeout,
                                       sdap_x_deref_parse_entry,
                                       state, SDAP_SRCH_FLG_PAGING);
    if (!subreq) {
        talloc_zfree(req);
        return NULL;
    }
    tevent_req_set_callback(subreq, sdap_x_deref_search_done, req);

    return req;
}

static int sdap_x_deref_create_control(struct sdap_handle *sh,
                                       const char *deref_attr,
                                       const char **attrs,
                                       LDAPControl **ctrl)
{
    struct berval derefval;
    int ret;
    struct LDAPDerefSpec ds[2];

    ds[0].derefAttr = discard_const(deref_attr);
    ds[0].attributes = discard_const(attrs);

    ds[1].derefAttr = NULL; /* sentinel */

    ret = ldap_create_deref_control_value(sh->ldap, ds, &derefval);
    if (ret != LDAP_SUCCESS) {
        DEBUG(SSSDBG_CRIT_FAILURE, "ldap_create_deref_control_value failed: %s\n",
                  ldap_err2string(ret));
        return ret;
    }

    ret = sdap_control_create(sh, LDAP_CONTROL_X_DEREF,
                              1, &derefval, 1, ctrl);
    ldap_memfree(derefval.bv_val);
    if (ret != EOK) {
        DEBUG(SSSDBG_CRIT_FAILURE, "sdap_control_create failed %d\n", ret);
        return ret;
    }

    return EOK;
}

static errno_t sdap_x_deref_parse_entry(struct sdap_handle *sh,
                                        struct sdap_msg *msg,
                                        void *pvt)
{
    errno_t ret;
    LDAPControl **ctrls = NULL;
    LDAPControl *derefctrl = NULL;
    LDAPDerefRes    *deref_res = NULL;
    LDAPDerefRes    *dref;
    struct sdap_deref_attrs **res;
    TALLOC_CTX *tmp_ctx;

    struct sdap_x_deref_search_state *state = talloc_get_type(pvt,
                                            struct sdap_x_deref_search_state);

    tmp_ctx = talloc_new(NULL);
    if (!tmp_ctx) return ENOMEM;

    ret = ldap_get_entry_controls(state->sh->ldap, msg->msg,
                                  &ctrls);
    if (ret != LDAP_SUCCESS) {
        DEBUG(SSSDBG_OP_FAILURE, "ldap_parse_result failed\n");
        goto done;
    }

    if (!ctrls) {
        /* When we attempt to request attributes that are not present in
         * the dereferenced links, some serves might not send the dereference
         * control back at all. Be permissive and treat the search as if
         * it didn't find anything.
         */
        DEBUG(SSSDBG_MINOR_FAILURE, "No controls found for entry\n");
        ret = EOK;
        goto done;
    }

    res = NULL;

    derefctrl = ldap_control_find(LDAP_CONTROL_X_DEREF, ctrls, NULL);
    if (!derefctrl) {
        DEBUG(SSSDBG_FUNC_DATA, "No deref controls found\n");
        ret = EOK;
        goto done;
    }

    DEBUG(SSSDBG_TRACE_FUNC, "Got deref control\n");

    ret = ldap_parse_derefresponse_control(state->sh->ldap,
                                            derefctrl,
                                            &deref_res);
    if (ret != LDAP_SUCCESS) {
        DEBUG(SSSDBG_OP_FAILURE,
              "ldap_parse_derefresponse_control failed: %s\n",
               ldap_err2string(ret));
        goto done;
    }

    for (dref = deref_res; dref; dref=dref->next) {
        ret = sdap_parse_deref(tmp_ctx, state->maps, state->num_maps,
                               dref, &res);
        if (ret) {
            DEBUG(SSSDBG_OP_FAILURE, "sdap_parse_deref failed [%d]: %s\n",
                  ret, strerror(ret));
            goto done;
        }

        ret = add_to_deref_reply(state, state->num_maps,
                                 &state->dreply, res);
        if (ret != EOK) {
            DEBUG(SSSDBG_OP_FAILURE, "add_to_deref_reply failed.\n");
            goto done;
        }
    }

    DEBUG(SSSDBG_TRACE_FUNC,
          "All deref results from a single control parsed\n");
    ldap_derefresponse_free(deref_res);
    deref_res = NULL;

    ret = EOK;
done:
    if (ret != EOK && ret != ENOMEM) {
        if (state->ldap_ignore_unreadable_references) {
            DEBUG(SSSDBG_TRACE_FUNC, "Ignoring unreadable reference\n");
            ret = EOK;
        }
    }

    talloc_zfree(tmp_ctx);
    ldap_controls_free(ctrls);
    ldap_derefresponse_free(deref_res);
    return ret;
}

static void sdap_x_deref_search_done(struct tevent_req *subreq)
{
    struct tevent_req *req = tevent_req_callback_data(subreq,
                                                      struct tevent_req);
    struct sdap_x_deref_search_state *state =
                tevent_req_data(req, struct sdap_x_deref_search_state);

    return generic_ext_search_handler(subreq, state->opts);
}

static int sdap_x_deref_search_ctrls_destructor(void *ptr)
{
    LDAPControl **ctrls = talloc_get_type(ptr, LDAPControl *);

    if (ctrls && ctrls[0]) {
        ldap_control_free(ctrls[0]);
    }

    return 0;
}

static int
sdap_x_deref_search_recv(struct tevent_req *req,
                         TALLOC_CTX *mem_ctx,
                         size_t *reply_count,
                         struct sdap_deref_attrs ***reply)
{
    struct sdap_x_deref_search_state *state = tevent_req_data(req,
                                            struct sdap_x_deref_search_state);

    TEVENT_REQ_RETURN_ON_ERROR(req);

    *reply_count = state->dreply.reply_count;
    *reply = talloc_steal(mem_ctx, state->dreply.reply);

    return EOK;
}

/* ==Security Descriptor (ACL) search=================================== */
struct sdap_sd_search_state {
  LDAPControl **ctrls;
  struct sdap_options *opts;
  size_t reply_count;
  struct sysdb_attrs **reply;
  struct sdap_reply sreply;

  /* Referrals returned by the search */
  size_t ref_count;
  char **refs;
};

static int sdap_sd_search_create_control(struct sdap_handle *sh,
					 int val,
					 LDAPControl **ctrl);
static int sdap_sd_search_ctrls_destructor(void *ptr);
static errno_t sdap_sd_search_parse_entry(struct sdap_handle *sh,
                                          struct sdap_msg *msg,
                                          void *pvt);
static void sdap_sd_search_done(struct tevent_req *subreq);

struct tevent_req *
sdap_sd_search_send(TALLOC_CTX *memctx, struct tevent_context *ev,
                     struct sdap_options *opts, struct sdap_handle *sh,
                     const char *base_dn, int sd_flags,
                     const char **attrs, int timeout)
{
    struct tevent_req *req = NULL;
    struct tevent_req *subreq = NULL;
    struct sdap_sd_search_state *state;
    int ret;

    req = tevent_req_create(memctx, &state, struct sdap_sd_search_state);
    if (!req) return NULL;

    state->ctrls = talloc_zero_array(state, LDAPControl *, 2);
    state->opts = opts;
    if (state->ctrls == NULL) {
        ret = EIO;
        goto fail;
    }
    talloc_set_destructor((TALLOC_CTX *) state->ctrls,
                          sdap_sd_search_ctrls_destructor);

    ret = sdap_sd_search_create_control(sh, sd_flags, &state->ctrls[0]);
    if (ret != EOK) {
        DEBUG(SSSDBG_CRIT_FAILURE, "Could not create SD control\n");
        ret = EIO;
        goto fail;
    }

    DEBUG(SSSDBG_TRACE_FUNC, "Searching entry [%s] using SD\n", base_dn);
    subreq = sdap_get_generic_ext_send(state, ev, opts, sh, base_dn,
                                       LDAP_SCOPE_BASE, "(objectclass=*)", attrs,
                                       state->ctrls, NULL, 0, timeout,
                                       sdap_sd_search_parse_entry,
                                       state, SDAP_SRCH_FLG_PAGING);
    if (!subreq) {
        ret = EIO;
        goto fail;
    }
    tevent_req_set_callback(subreq, sdap_sd_search_done, req);
    return req;

fail:
    tevent_req_error(req, ret);
    tevent_req_post(req, ev);
    return req;
}

static int sdap_sd_search_create_control(struct sdap_handle *sh,
					 int val,
					 LDAPControl **ctrl)
{
    struct berval *sdval;
    int ret;
    BerElement *ber = NULL;
    ber = ber_alloc_t(LBER_USE_DER);
    if (ber == NULL) {
        DEBUG(SSSDBG_OP_FAILURE, "ber_alloc_t failed.\n");
        return ENOMEM;
    }

    ret = ber_printf(ber, "{i}", val);
    if (ret == -1) {
        DEBUG(SSSDBG_OP_FAILURE, "ber_printf failed.\n");
        ber_free(ber, 1);
        return EIO;
    }

    ret = ber_flatten(ber, &sdval);
    ber_free(ber, 1);
    if (ret == -1) {
        DEBUG(SSSDBG_CRIT_FAILURE, "ber_flatten failed.\n");
        return EIO;
    }

    ret = sdap_control_create(sh, LDAP_SERVER_SD_OID, 1, sdval, 1, ctrl);
    ber_bvfree(sdval);
    if (ret != EOK) {
        DEBUG(SSSDBG_CRIT_FAILURE, "sdap_control_create failed\n");
        return ret;
    }

    return EOK;
}

static errno_t sdap_sd_search_parse_entry(struct sdap_handle *sh,
                                          struct sdap_msg *msg,
                                          void *pvt)
{
    errno_t ret;
    struct sysdb_attrs *attrs;
    struct sdap_sd_search_state *state =
                talloc_get_type(pvt, struct sdap_sd_search_state);

    bool disable_range_rtrvl = dp_opt_get_bool(state->opts->basic,
                                               SDAP_DISABLE_RANGE_RETRIEVAL);

    ret = sdap_parse_entry(state, sh, msg,
                           NULL, 0,
                           &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_sd_search_done(struct tevent_req *subreq)
{
    int ret;

    struct tevent_req *req = tevent_req_callback_data(subreq,
                                                      struct tevent_req);
    struct sdap_sd_search_state *state =
                tevent_req_data(req, struct sdap_sd_search_state);

    ret = sdap_get_generic_ext_recv(subreq, state,
                                    &state->ref_count,
                                    &state->refs);
    talloc_zfree(subreq);

    if (ret != EOK) {
        if (ret == ETIMEDOUT) {
            DEBUG(SSSDBG_CRIT_FAILURE,
                  "sdap_get_generic_ext_recv request failed: [%d]: %s "
                  "[ldap_network_timeout]\n",
                  ret, sss_strerror(ret));
        } else {
            DEBUG(SSSDBG_CRIT_FAILURE,
                  "sdap_get_generic_ext_recv request failed: [%d]: %s\n",
                  ret, sss_strerror(ret));
        }
        tevent_req_error(req, ret);
        return;
    }

    tevent_req_done(req);
}

static int sdap_sd_search_ctrls_destructor(void *ptr)
{
    LDAPControl **ctrls = talloc_get_type(ptr, LDAPControl *);
    if (ctrls && ctrls[0]) {
        ldap_control_free(ctrls[0]);
    }

    return 0;
}

int sdap_sd_search_recv(struct tevent_req *req,
                        TALLOC_CTX *mem_ctx,
                        size_t *_reply_count,
                        struct sysdb_attrs ***_reply,
                        size_t *_ref_count,
                        char ***_refs)
{
    struct sdap_sd_search_state *state = tevent_req_data(req,
                                            struct sdap_sd_search_state);
    TEVENT_REQ_RETURN_ON_ERROR(req);

    *_reply_count = state->sreply.reply_count;
    *_reply = talloc_steal(mem_ctx, state->sreply.reply);

    if(_ref_count) {
        *_ref_count = state->ref_count;
    }

    if (_refs) {
        *_refs = talloc_steal(mem_ctx, state->refs);
    }

    return EOK;
}

/* ==Attribute scoped search============================================ */
struct sdap_asq_search_state {
    struct sdap_attr_map_info *maps;
    int num_maps;
    LDAPControl **ctrls;
    struct sdap_options *opts;
    bool ldap_ignore_unreadable_references;

    struct sdap_deref_reply dreply;
};

static int sdap_asq_search_create_control(struct sdap_handle *sh,
                                          const char *attr,
                                          LDAPControl **ctrl);
static int sdap_asq_search_ctrls_destructor(void *ptr);
static errno_t sdap_asq_search_parse_entry(struct sdap_handle *sh,
                                           struct sdap_msg *msg,
                                           void *pvt);
static void sdap_asq_search_done(struct tevent_req *subreq);

static struct tevent_req *
sdap_asq_search_send(TALLOC_CTX *memctx, struct tevent_context *ev,
                     struct sdap_options *opts, struct sdap_handle *sh,
                     const char *base_dn, const char *deref_attr,
                     const char **attrs, struct sdap_attr_map_info *maps,
                     int num_maps, int timeout)
{
    struct tevent_req *req = NULL;
    struct tevent_req *subreq = NULL;
    struct sdap_asq_search_state *state;
    int ret;

    req = tevent_req_create(memctx, &state, struct sdap_asq_search_state);
    if (!req) return NULL;

    state->maps = maps;
    state->num_maps = num_maps;
    state->ctrls = talloc_zero_array(state, LDAPControl *, 2);
    state->opts = opts;
    if (state->ctrls == NULL) {
        talloc_zfree(req);
        return NULL;
    }
    talloc_set_destructor((TALLOC_CTX *) state->ctrls,
                          sdap_asq_search_ctrls_destructor);

    state->ldap_ignore_unreadable_references = dp_opt_get_bool(opts->basic,
                                            SDAP_IGNORE_UNREADABLE_REFERENCES);

    ret = sdap_asq_search_create_control(sh, deref_attr, &state->ctrls[0]);
    if (ret != EOK) {
        talloc_zfree(req);
        DEBUG(SSSDBG_CRIT_FAILURE, "Could not create ASQ control\n");
        return NULL;
    }

    DEBUG(SSSDBG_TRACE_FUNC, "Dereferencing entry [%s] using ASQ\n", base_dn);
    subreq = sdap_get_generic_ext_send(state, ev, opts, sh, base_dn,
                                       LDAP_SCOPE_BASE, NULL, attrs,
                                       state->ctrls, NULL, 0, timeout,
                                       sdap_asq_search_parse_entry,
                                       state, SDAP_SRCH_FLG_PAGING);
    if (!subreq) {
        talloc_zfree(req);
        return NULL;
    }
    tevent_req_set_callback(subreq, sdap_asq_search_done, req);

    return req;
}


static int sdap_asq_search_create_control(struct sdap_handle *sh,
                                          const char *attr,
                                          LDAPControl **ctrl)
{
    struct berval *asqval;
    int ret;
    BerElement *ber = NULL;

    ber = ber_alloc_t(LBER_USE_DER);
    if (ber == NULL) {
        DEBUG(SSSDBG_OP_FAILURE, "ber_alloc_t failed.\n");
        return ENOMEM;
    }

    ret = ber_printf(ber, "{s}", attr);
    if (ret == -1) {
        DEBUG(SSSDBG_OP_FAILURE, "ber_printf failed.\n");
        ber_free(ber, 1);
        return EIO;
    }

    ret = ber_flatten(ber, &asqval);
    ber_free(ber, 1);
    if (ret == -1) {
        DEBUG(SSSDBG_CRIT_FAILURE, "ber_flatten failed.\n");
        return EIO;
    }

    ret = sdap_control_create(sh, LDAP_SERVER_ASQ_OID, 1, asqval, 1, ctrl);
    ber_bvfree(asqval);
    if (ret != EOK) {
        DEBUG(SSSDBG_CRIT_FAILURE, "sdap_control_create failed\n");
        return ret;
    }

    return EOK;
}

static errno_t sdap_asq_search_parse_entry(struct sdap_handle *sh,
                                           struct sdap_msg *msg,
                                           void *pvt)
{
    errno_t ret;
    struct sdap_asq_search_state *state =
                talloc_get_type(pvt, struct sdap_asq_search_state);
    struct berval **vals;
    int i, mi;
    struct sdap_attr_map *map;
    int num_attrs = 0;
    struct sdap_deref_attrs **res;
    char *tmp;
    char *dn = NULL;
    TALLOC_CTX *tmp_ctx;
    bool disable_range_rtrvl;

    tmp_ctx = talloc_new(NULL);
    if (!tmp_ctx) return ENOMEM;

    res = talloc_array(tmp_ctx, struct sdap_deref_attrs *,
                       state->num_maps);
    if (!res) {
        ret = ENOMEM;
        goto done;
    }

    for (mi =0; mi < state->num_maps; mi++) {
        res[mi] = talloc_zero(res, struct sdap_deref_attrs);
        if (!res[mi]) {
            ret = ENOMEM;
            goto done;
        }
        res[mi]->map = state->maps[mi].map;
        res[mi]->attrs = NULL;
    }


    tmp = ldap_get_dn(sh->ldap, msg->msg);
    if (!tmp) {
        ret = EINVAL;
        goto done;
    }

    dn = talloc_strdup(tmp_ctx, tmp);
    ldap_memfree(tmp);
    if (!dn) {
        ret = ENOMEM;
        goto done;
    }

    /* Find all suitable maps in the list */
    vals = ldap_get_values_len(sh->ldap, msg->msg, "objectClass");
    if (!vals) {
        DEBUG(SSSDBG_OP_FAILURE,
              "Unknown entry type, no objectClass found for DN [%s]!\n", dn);
        ret = EINVAL;
        goto done;
    }
    for (mi =0; mi < state->num_maps; mi++) {
        map = NULL;
        for (i = 0; vals[i]; i++) {
            /* the objectclass is always the first name in the map */
            if (strncasecmp(state->maps[mi].map[0].name,
                            vals[i]->bv_val, vals[i]->bv_len) == 0) {
                /* it's an entry of the right type */
                DEBUG(SSSDBG_TRACE_INTERNAL,
                      "Matched objectclass [%s] on DN [%s], will use associated map\n",
                       state->maps[mi].map[0].name, dn);
                map = state->maps[mi].map;
                num_attrs = state->maps[mi].num_attrs;
                break;
            }
        }
        if (!map) {
            DEBUG(SSSDBG_TRACE_INTERNAL,
                  "DN [%s] did not match the objectClass [%s]\n",
                   dn, state->maps[mi].map[0].name);
            continue;
        }

        disable_range_rtrvl = dp_opt_get_bool(state->opts->basic,
                                              SDAP_DISABLE_RANGE_RETRIEVAL);

        ret = sdap_parse_entry(res[mi], sh, msg,
                               map, num_attrs,
                               &res[mi]->attrs, disable_range_rtrvl);
        if (ret != EOK) {
            DEBUG(SSSDBG_MINOR_FAILURE,
                  "sdap_parse_entry failed [%d]: %s\n", ret, strerror(ret));
            goto done;
        }
    }
    ldap_value_free_len(vals);

    ret = add_to_deref_reply(state, state->num_maps,
                             &state->dreply, res);
    if (ret != EOK) {
        DEBUG(SSSDBG_CRIT_FAILURE, "add_to_deref_reply failed.\n");
        goto done;
    }

    ret = EOK;
done:
    if (ret != EOK && ret != ENOMEM) {
        if (state->ldap_ignore_unreadable_references) {
            DEBUG(SSSDBG_TRACE_FUNC, "Ignoring unreadable reference [%s]\n",
                  dn != NULL ? dn : "(null)");
            ret = EOK;
        }
    }
    talloc_zfree(tmp_ctx);
    return ret;
}

static void sdap_asq_search_done(struct tevent_req *subreq)
{
    struct tevent_req *req = tevent_req_callback_data(subreq,
                                                      struct tevent_req);
    struct sdap_asq_search_state *state =
                tevent_req_data(req, struct sdap_asq_search_state);

    return generic_ext_search_handler(subreq, state->opts);
}

static int sdap_asq_search_ctrls_destructor(void *ptr)
{
    LDAPControl **ctrls = talloc_get_type(ptr, LDAPControl *);

    if (ctrls && ctrls[0]) {
        ldap_control_free(ctrls[0]);
    }

    return 0;
}

int sdap_asq_search_recv(struct tevent_req *req,
                         TALLOC_CTX *mem_ctx,
                         size_t *reply_count,
                         struct sdap_deref_attrs ***reply)
{
    struct sdap_asq_search_state *state = tevent_req_data(req,
                                            struct sdap_asq_search_state);
    TEVENT_REQ_RETURN_ON_ERROR(req);

    *reply_count = state->dreply.reply_count;
    *reply = talloc_steal(mem_ctx, state->dreply.reply);

    return EOK;
}

/* ==Generic Deref Search============================================ */
enum sdap_deref_type {
    SDAP_DEREF_OPENLDAP,
    SDAP_DEREF_ASQ
};

struct sdap_deref_search_state {
    struct sdap_handle *sh;
    const char *base_dn;
    const char *deref_attr;

    size_t reply_count;
    struct sdap_deref_attrs **reply;
    enum sdap_deref_type deref_type;
    unsigned flags;
};

static void sdap_deref_search_done(struct tevent_req *subreq);
static void sdap_deref_search_with_filter_done(struct tevent_req *subreq);

struct tevent_req *
sdap_deref_search_with_filter_send(TALLOC_CTX *memctx,
                                   struct tevent_context *ev,
                                   struct sdap_options *opts,
                                   struct sdap_handle *sh,
                                   const char *search_base,
                                   const char *filter,
                                   const char *deref_attr,
                                   const char **attrs,
                                   int num_maps,
                                   struct sdap_attr_map_info *maps,
                                   int timeout,
                                   unsigned flags)
{
    struct tevent_req *req = NULL;
    struct tevent_req *subreq = NULL;
    struct sdap_deref_search_state *state;

    req = tevent_req_create(memctx, &state, struct sdap_deref_search_state);
    if (!req) return NULL;

    state->sh = sh;
    state->reply_count = 0;
    state->reply = NULL;
    state->flags = flags;

    if (sdap_is_control_supported(sh, LDAP_CONTROL_X_DEREF)) {
        DEBUG(SSSDBG_TRACE_INTERNAL, "Server supports OpenLDAP deref\n");
        state->deref_type = SDAP_DEREF_OPENLDAP;

        subreq = sdap_x_deref_search_send(state, ev, opts, sh, search_base,
                                          filter, deref_attr, attrs, maps,
                                          num_maps, timeout);
        if (!subreq) {
            DEBUG(SSSDBG_OP_FAILURE, "Cannot start OpenLDAP deref search\n");
            goto fail;
        }
    } else {
        DEBUG(SSSDBG_OP_FAILURE,
              "Server does not support any known deref method!\n");
        goto fail;
    }

    tevent_req_set_callback(subreq, sdap_deref_search_with_filter_done, req);
    return req;

fail:
    talloc_zfree(req);
    return NULL;
}

static void sdap_deref_search_with_filter_done(struct tevent_req *subreq)
{
    sdap_deref_search_done(subreq);
}

int sdap_deref_search_with_filter_recv(struct tevent_req *req,
                                       TALLOC_CTX *mem_ctx,
                                       size_t *reply_count,
                                       struct sdap_deref_attrs ***reply)
{
    return sdap_deref_search_recv(req, mem_ctx, reply_count, reply);
}

struct tevent_req *
sdap_deref_search_send(TALLOC_CTX *memctx,
                       struct tevent_context *ev,
                       struct sdap_options *opts,
                       struct sdap_handle *sh,
                       const char *base_dn,
                       const char *deref_attr,
                       const char **attrs,
                       int num_maps,
                       struct sdap_attr_map_info *maps,
                       int timeout)
{
    struct tevent_req *req = NULL;
    struct tevent_req *subreq = NULL;
    struct sdap_deref_search_state *state;

    req = tevent_req_create(memctx, &state, struct sdap_deref_search_state);
    if (!req) return NULL;

    state->sh = sh;
    state->reply_count = 0;
    state->reply = NULL;
    state->base_dn = base_dn;
    state->deref_attr = deref_attr;

    PROBE(SDAP_DEREF_SEARCH_SEND, state->base_dn, state->deref_attr);

    if (sdap_is_control_supported(sh, LDAP_SERVER_ASQ_OID)) {
        DEBUG(SSSDBG_TRACE_INTERNAL, "Server supports ASQ\n");
        state->deref_type = SDAP_DEREF_ASQ;

        subreq = sdap_asq_search_send(state, ev, opts, sh, base_dn,
                                      deref_attr, attrs, maps, num_maps,
                                      timeout);
        if (!subreq) {
            DEBUG(SSSDBG_OP_FAILURE, "Cannot start ASQ search\n");
            goto fail;
        }
    } else if (sdap_is_control_supported(sh, LDAP_CONTROL_X_DEREF)) {
        DEBUG(SSSDBG_TRACE_INTERNAL, "Server supports OpenLDAP deref\n");
        state->deref_type = SDAP_DEREF_OPENLDAP;

        subreq = sdap_x_deref_search_send(state, ev, opts, sh, base_dn, NULL,
                                          deref_attr, attrs, maps, num_maps,
                                          timeout);
        if (!subreq) {
            DEBUG(SSSDBG_OP_FAILURE, "Cannot start OpenLDAP deref search\n");
            goto fail;
        }
    } else {
        DEBUG(SSSDBG_OP_FAILURE,
              "Server does not support any known deref method!\n");
        goto fail;
    }

    tevent_req_set_callback(subreq, sdap_deref_search_done, req);
    return req;

fail:
    talloc_zfree(req);
    return NULL;
}

static void sdap_deref_search_done(struct tevent_req *subreq)
{
    struct tevent_req *req = tevent_req_callback_data(subreq,
                                                      struct tevent_req);
    struct sdap_deref_search_state *state = tevent_req_data(req,
                                            struct sdap_deref_search_state);
    int ret;

    switch (state->deref_type) {
    case SDAP_DEREF_OPENLDAP:
        ret = sdap_x_deref_search_recv(subreq, state,
                &state->reply_count, &state->reply);
        break;
    case SDAP_DEREF_ASQ:
        ret = sdap_asq_search_recv(subreq, state,
                &state->reply_count, &state->reply);
        break;
    default:
        DEBUG(SSSDBG_CRIT_FAILURE,
              "Unknown deref method %d\n", state->deref_type);
        tevent_req_error(req, EINVAL);
        return;
    }

    talloc_zfree(subreq);
    if (ret != EOK) {
        DEBUG(SSSDBG_OP_FAILURE,
              "dereference processing failed [%d]: %s\n", ret, strerror(ret));
        if (ret == ENOTSUP) {
            state->sh->disable_deref = true;
        }

        if (!(state->flags & SDAP_DEREF_FLG_SILENT)) {
            if (ret == ENOTSUP) {
                sss_log(SSS_LOG_WARNING,
                        "LDAP server claims to support deref, but deref search "
                        "failed. Disabling deref for further requests. You can "
                        "permanently disable deref by setting "
                        "ldap_deref_threshold to 0 in domain configuration.");
            } else {
                sss_log(SSS_LOG_WARNING,
                        "dereference processing failed : %s", strerror(ret));
            }
        }
        tevent_req_error(req, ret);
        return;
    }

    tevent_req_done(req);
}

int sdap_deref_search_recv(struct tevent_req *req,
                           TALLOC_CTX *mem_ctx,
                           size_t *reply_count,
                           struct sdap_deref_attrs ***reply)
{
    struct sdap_deref_search_state *state = tevent_req_data(req,
                                            struct sdap_deref_search_state);

    PROBE(SDAP_DEREF_SEARCH_RECV, state->base_dn, state->deref_attr);

    TEVENT_REQ_RETURN_ON_ERROR(req);

    *reply_count = state->reply_count;
    *reply = talloc_steal(mem_ctx, state->reply);

    return EOK;
}

bool sdap_has_deref_support_ex(struct sdap_handle *sh,
                               struct sdap_options *opts,
                               bool ignore_client)
{
    const char *deref_oids[][2] = { { LDAP_SERVER_ASQ_OID, "ASQ" },
                                    { LDAP_CONTROL_X_DEREF, "OpenLDAP" },
                                    { NULL, NULL }
                                  };
    int i;
    int deref_threshold;

    if (sh->disable_deref) {
        return false;
    }

    if (ignore_client == false) {
        deref_threshold = dp_opt_get_int(opts->basic, SDAP_DEREF_THRESHOLD);
        if (deref_threshold == 0) {
            return false;
        }
    }

    for (i=0; deref_oids[i][0]; i++) {
        if (sdap_is_control_supported(sh, deref_oids[i][0])) {
            DEBUG(SSSDBG_TRACE_FUNC, "The server supports deref method %s\n",
                  deref_oids[i][1]);
            return true;
        }
    }

    return false;
}

bool sdap_has_deref_support(struct sdap_handle *sh, struct sdap_options *opts)
{
    return sdap_has_deref_support_ex(sh, opts, false);
}
