static NTSTATUS ldapsrv_SearchRequest(struct ldapsrv_call *call)
{
	struct ldap_SearchRequest *req = &call->request->r.SearchRequest;
	struct ldap_Result *done;
	struct ldapsrv_reply *done_r;
	TALLOC_CTX *local_ctx;
	struct ldapsrv_context *callback_ctx = NULL;
	struct ldb_context *samdb = talloc_get_type(call->conn->ldb, struct ldb_context);
	struct ldb_dn *basedn;
	struct ldb_request *lreq;
	struct ldb_control *search_control;
	struct ldb_search_options_control *search_options;
	struct ldb_control *extended_dn_control;
	struct ldb_extended_dn_control *extended_dn_decoded = NULL;
	struct ldb_control *notification_control = NULL;
	enum ldb_scope scope = LDB_SCOPE_DEFAULT;
	const char **attrs = NULL;
	const char *scope_str, *errstr = NULL;
	int result = -1;
	int ldb_ret = -1;
	unsigned int i;
	int extended_type = 1;

	/*
	 * Warn for searches that are longer than 1/4 of the
	 * search_timeout, being 30sec by default
	 */
	struct timeval start_time = timeval_current();
	struct timeval warning_time
		= timeval_add(&start_time,
			      call->conn->limits.search_timeout / 4,
			      0);

	local_ctx = talloc_new(call);
	NT_STATUS_HAVE_NO_MEMORY(local_ctx);

	basedn = ldb_dn_new(local_ctx, samdb, req->basedn);
	NT_STATUS_HAVE_NO_MEMORY(basedn);

	switch (req->scope) {
	case LDAP_SEARCH_SCOPE_BASE:
		scope_str = "BASE";
		scope = LDB_SCOPE_BASE;
		break;
	case LDAP_SEARCH_SCOPE_SINGLE:
		scope_str = "ONE";
		scope = LDB_SCOPE_ONELEVEL;
		break;
	case LDAP_SEARCH_SCOPE_SUB:
		scope_str = "SUB";
		scope = LDB_SCOPE_SUBTREE;
		break;
	default:
		result = LDAP_PROTOCOL_ERROR;
		map_ldb_error(local_ctx, LDB_ERR_PROTOCOL_ERROR, NULL,
			      &errstr);
		scope_str = "<Invalid scope>";
		errstr = talloc_asprintf(local_ctx,
					 "%s. Invalid scope", errstr);
		goto reply;
	}
	DEBUG(10,("SearchRequest: scope: [%s]\n", scope_str));

	if (req->num_attributes >= 1) {
		attrs = talloc_array(local_ctx, const char *, req->num_attributes+1);
		NT_STATUS_HAVE_NO_MEMORY(attrs);

		for (i=0; i < req->num_attributes; i++) {
			DEBUG(10,("SearchRequest: attrs: [%s]\n",req->attributes[i]));
			attrs[i] = req->attributes[i];
		}
		attrs[i] = NULL;
	}

	DEBUG(5,("ldb_request %s dn=%s filter=%s\n", 
		 scope_str, req->basedn, ldb_filter_from_tree(call, req->tree)));

	callback_ctx = talloc_zero(local_ctx, struct ldapsrv_context);
	NT_STATUS_HAVE_NO_MEMORY(callback_ctx);
	callback_ctx->call = call;
	callback_ctx->extended_type = extended_type;
	callback_ctx->attributesonly = req->attributesonly;

	ldb_ret = ldb_build_search_req_ex(&lreq, samdb, local_ctx,
					  basedn, scope,
					  req->tree, attrs,
					  call->request->controls,
					  callback_ctx,
					  ldap_server_search_callback,
					  NULL);

	if (ldb_ret != LDB_SUCCESS) {
		goto reply;
	}

	if (call->conn->global_catalog) {
		search_control = ldb_request_get_control(lreq, LDB_CONTROL_SEARCH_OPTIONS_OID);

		search_options = NULL;
		if (search_control) {
			search_options = talloc_get_type(search_control->data, struct ldb_search_options_control);
			search_options->search_options |= LDB_SEARCH_OPTION_PHANTOM_ROOT;
		} else {
			search_options = talloc(lreq, struct ldb_search_options_control);
			NT_STATUS_HAVE_NO_MEMORY(search_options);
			search_options->search_options = LDB_SEARCH_OPTION_PHANTOM_ROOT;
			ldb_request_add_control(lreq, LDB_CONTROL_SEARCH_OPTIONS_OID, false, search_options);
		}
	} else {
		ldb_request_add_control(lreq, DSDB_CONTROL_NO_GLOBAL_CATALOG, false, NULL);
	}

	extended_dn_control = ldb_request_get_control(lreq, LDB_CONTROL_EXTENDED_DN_OID);

	if (extended_dn_control) {
		if (extended_dn_control->data) {
			extended_dn_decoded = talloc_get_type(extended_dn_control->data, struct ldb_extended_dn_control);
			extended_type = extended_dn_decoded->type;
		} else {
			extended_type = 0;
		}
		callback_ctx->extended_type = extended_type;
	}

	notification_control = ldb_request_get_control(lreq, LDB_CONTROL_NOTIFICATION_OID);
	if (notification_control != NULL) {
		const struct ldapsrv_call *pc = NULL;
		size_t count = 0;

		for (pc = call->conn->pending_calls; pc != NULL; pc = pc->next) {
			count += 1;
		}

		if (count >= call->conn->limits.max_notifications) {
			DEBUG(10,("SearchRequest: error MaxNotificationPerConn\n"));
			result = map_ldb_error(local_ctx,
					       LDB_ERR_ADMIN_LIMIT_EXCEEDED,
					       "MaxNotificationPerConn reached",
					       &errstr);
			goto reply;
		}

		/*
		 * For now we need to do periodic retries on our own.
		 * As the dsdb_notification module will return after each run.
		 */
		call->notification.busy = true;
	}

	{
		const char *scheme = NULL;
		switch (call->conn->referral_scheme) {
		case LDAP_REFERRAL_SCHEME_LDAPS:
			scheme = "ldaps";
			break;
		default:
			scheme = "ldap";
		}
		ldb_ret = ldb_set_opaque(
			samdb,
			LDAP_REFERRAL_SCHEME_OPAQUE,
			discard_const_p(char *, scheme));
		if (ldb_ret != LDB_SUCCESS) {
			goto reply;
		}
	}

	{
		time_t timeout = call->conn->limits.search_timeout;

		if (timeout == 0
		    || (req->timelimit != 0
			&& req->timelimit < timeout))
		{
			timeout = req->timelimit;
		}
		ldb_set_timeout(samdb, lreq, timeout);
	}

	if (!call->conn->is_privileged) {
		ldb_req_mark_untrusted(lreq);
	}

	LDB_REQ_SET_LOCATION(lreq);

	ldb_ret = ldb_request(samdb, lreq);

	if (ldb_ret != LDB_SUCCESS) {
		goto reply;
	}

	ldb_ret = ldb_wait(lreq->handle, LDB_WAIT_ALL);

	if (ldb_ret == LDB_SUCCESS) {
		if (call->notification.busy) {
			/* Move/Add it to the end */
			DLIST_DEMOTE(call->conn->pending_calls, call);
			call->notification.generation =
				call->conn->service->notification.generation;

			if (callback_ctx->count != 0) {
				call->notification.generation += 1;
				ldapsrv_notification_retry_setup(call->conn->service,
								 true);
			}

			talloc_free(local_ctx);
			return NT_STATUS_OK;
		}
	}

reply:

	/*
	 * This looks like duplicated code - because it is - but
	 * otherwise the work in the parameters will be done
	 * regardless, this way the functions only execuate when the
	 * log level is set.
	 *
	 * The basedn is re-obtained as a string to escape it
	 */
	if ((req->timelimit == 0 || call->conn->limits.search_timeout < req->timelimit)
	    && ldb_ret == LDB_ERR_TIME_LIMIT_EXCEEDED) {
		struct dom_sid_buf sid_buf;
		DBG_WARNING("MaxQueryDuration(%d) timeout exceeded "
			    "in SearchRequest by %s from %s filter: [%s] "
			    "basedn: [%s] "
			    "scope: [%s]\n",
			    call->conn->limits.search_timeout,
			    dom_sid_str_buf(&call->conn->session_info->security_token->sids[0],
					    &sid_buf),
			    tsocket_address_string(call->conn->connection->remote_address,
						   call),
			    ldb_filter_from_tree(call, req->tree),
			    ldb_dn_get_extended_linearized(call, basedn, 1),
			    scope_str);
		for (i=0; i < req->num_attributes; i++) {
			DBG_WARNING("MaxQueryDuration timeout exceeded attrs: [%s]\n",
				    req->attributes[i]);
		}

	} else if (timeval_expired(&warning_time)) {
		struct dom_sid_buf sid_buf;
		DBG_NOTICE("Long LDAP Query: Duration was %.2fs, "
			   "MaxQueryDuration(%d)/4 == %d "
			   "in SearchRequest by %s from %s filter: [%s] "
			   "basedn: [%s] "
			   "scope: [%s] "
			   "result: %s\n",
			   timeval_elapsed(&start_time),
			   call->conn->limits.search_timeout,
			   call->conn->limits.search_timeout / 4,
			   dom_sid_str_buf(&call->conn->session_info->security_token->sids[0],
					   &sid_buf),
			   tsocket_address_string(call->conn->connection->remote_address,
						  call),
			   ldb_filter_from_tree(call, req->tree),
			   ldb_dn_get_extended_linearized(call, basedn, 1),
			   scope_str,
			   ldb_strerror(ldb_ret));
		for (i=0; i < req->num_attributes; i++) {
			DBG_NOTICE("Long LDAP Query attrs: [%s]\n",
				   req->attributes[i]);
		}
	} else {
		struct dom_sid_buf sid_buf;
		DBG_INFO("LDAP Query: Duration was %.2fs, "
			 "SearchRequest by %s from %s filter: [%s] "
			 "basedn: [%s] "
			 "scope: [%s] "
			 "result: %s\n",
			 timeval_elapsed(&start_time),
			 dom_sid_str_buf(&call->conn->session_info->security_token->sids[0],
					 &sid_buf),
			 tsocket_address_string(call->conn->connection->remote_address,
						call),
			 ldb_filter_from_tree(call, req->tree),
			 ldb_dn_get_extended_linearized(call, basedn, 1),
			 scope_str,
			 ldb_strerror(ldb_ret));
	}

	DLIST_REMOVE(call->conn->pending_calls, call);
	call->notification.busy = false;

	done_r = ldapsrv_init_reply(call, LDAP_TAG_SearchResultDone);
	NT_STATUS_HAVE_NO_MEMORY(done_r);

	done = &done_r->msg->r.SearchResultDone;
	done->dn = NULL;
	done->referral = NULL;

	if (result != -1) {
	} else if (ldb_ret == LDB_SUCCESS) {
		if (callback_ctx->controls) {
			done_r->msg->controls = callback_ctx->controls;
			talloc_steal(done_r->msg, callback_ctx->controls);
		}
		result = LDB_SUCCESS;
	} else {
		DEBUG(10,("SearchRequest: error\n"));
		result = map_ldb_error(local_ctx, ldb_ret, ldb_errstring(samdb),
				       &errstr);
	}

	done->resultcode = result;
	done->errormessage = (errstr?talloc_strdup(done_r, errstr):NULL);

	talloc_free(local_ctx);

	return ldapsrv_queue_reply_forced(call, done_r);
}