summaryrefslogtreecommitdiffstats
path: root/src/providers/ldap/sdap_async.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/providers/ldap/sdap_async.c')
-rw-r--r--src/providers/ldap/sdap_async.c3169
1 files changed, 3169 insertions, 0 deletions
diff --git a/src/providers/ldap/sdap_async.c b/src/providers/ldap/sdap_async.c
new file mode 100644
index 0000000..ab3572d
--- /dev/null
+++ b/src/providers/ldap/sdap_async.c
@@ -0,0 +1,3169 @@
+/*
+ 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_REALLOC_INCREMENT 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(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(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(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(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)
+{
+ 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;
+ }
+
+ 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;
+};
+
+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 += REPLY_REALLOC_INCREMENT;
+ 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;
+}
+
+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 += REPLY_REALLOC_INCREMENT;
+ 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);
+ char *errmsg;
+ int lret;
+ int optret;
+ 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);
+
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "calling ldap_search_ext with [%s][%s].\n",
+ state->filter ? state->filter : "no filter",
+ state->search_base);
+ if (state->attrs) {
+ for (int i = 0; state->attrs[i]; i++) {
+ DEBUG(SSSDBG_TRACE_LIBS,
+ "Requesting attrs: [%s]\n", state->attrs[i]);
+ }
+ }
+
+ 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;
+ optret = sss_ldap_get_diagnostic_msg(state, state->sh->ldap,
+ &errmsg);
+ if (optret == LDAP_SUCCESS) {
+ DEBUG(SSSDBG_MINOR_FAILURE, "Connection error: %s\n", errmsg);
+ sss_log(SSS_LOG_ERR, "LDAP connection error: %s", errmsg);
+ } else {
+ sss_log(SSS_LOG_ERR, "LDAP connection error, %s",
+ sss_ldap_err2string(lret));
+ }
+ } else if (lret == LDAP_FILTER_ERROR) {
+ ret = ERR_INVALID_FILTER;
+ } else {
+ ret = EIO;
+ }
+ goto done;
+ }
+ DEBUG(SSSDBG_TRACE_INTERNAL, "ldap_search_ext called, msgid = %d\n", msgid);
+
+ 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");
+ }
+
+ 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======================= */
+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);
+}