/*
   Unix SMB/CIFS implementation.
   Main metadata server / Spotlight client functions

   Copyright (C) Ralph Boehme 2019

   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 "includes.h"
#include "rpc_client.h"
#include "../librpc/gen_ndr/ndr_mdssvc_c.h"
#include "lib/util/tevent_ntstatus.h"
#include "rpc_server/mdssvc/dalloc.h"
#include "rpc_server/mdssvc/marshalling.h"
#include "cli_mdssvc.h"
#include "cli_mdssvc_private.h"
#include "cli_mdssvc_util.h"

struct mdsctx_id mdscli_new_ctx_id(struct mdscli_ctx *mdscli_ctx)
{
	mdscli_ctx->ctx_id.id++;
	return mdscli_ctx->ctx_id;
}

char *mdscli_get_basepath(TALLOC_CTX *mem_ctx,
			  struct mdscli_ctx *mdscli_ctx)
{
	return talloc_strdup(mem_ctx, mdscli_ctx->mdscmd_open.share_path);
}

struct mdscli_connect_state {
	struct tevent_context *ev;
	struct mdscli_ctx *mdscli_ctx;
	struct mdssvc_blob response_blob;
};

static void mdscli_connect_open_done(struct tevent_req *subreq);
static void mdscli_connect_unknown1_done(struct tevent_req *subreq);
static void mdscli_connect_fetch_props_done(struct tevent_req *subreq);

struct tevent_req *mdscli_connect_send(TALLOC_CTX *mem_ctx,
				       struct tevent_context *ev,
				       struct dcerpc_binding_handle *bh,
				       const char *share_name,
				       const char *mount_path)
{
	struct tevent_req *req = NULL;
	struct mdscli_connect_state *state = NULL;
	struct tevent_req *subreq = NULL;
	struct mdscli_ctx *ctx = NULL;

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

	ctx = talloc_zero(state, struct mdscli_ctx);
	if (tevent_req_nomem(ctx, req)) {
		return tevent_req_post(req, ev);
	}

	*state = (struct mdscli_connect_state) {
		.ev = ev,
		.mdscli_ctx = ctx,
	};

	*ctx = (struct mdscli_ctx) {
		.bh = bh,
		.max_fragment_size = 64 * 1024,
		/*
		 * The connection id is a per tcon value sent by the client,
		 * 0x6b000060 is a value used most of the times for the first
		 * tcon.
		 */
		.ctx_id.connection = UINT64_C(0x6b000060),
	};

	subreq = dcerpc_mdssvc_open_send(state,
					 state->ev,
					 ctx->bh,
					 &ctx->dev,
					 &ctx->mdscmd_open.unkn2,
					 &ctx->mdscmd_open.unkn3,
					 mount_path,
					 share_name,
					 ctx->mdscmd_open.share_path,
					 &ctx->ph);
	if (tevent_req_nomem(subreq, req)) {
		return tevent_req_post(req, state->ev);
	}
	tevent_req_set_callback(subreq, mdscli_connect_open_done, req);
	ctx->async_pending++;

	return req;
}

static void mdscli_connect_open_done(struct tevent_req *subreq)
{
	struct tevent_req *req = tevent_req_callback_data(
		subreq, struct tevent_req);
	struct mdscli_connect_state *state = tevent_req_data(
		req, struct mdscli_connect_state);
	struct mdscli_ctx *mdscli_ctx = state->mdscli_ctx;
	size_t share_path_len;
	NTSTATUS status;

	status = dcerpc_mdssvc_open_recv(subreq, state);
	TALLOC_FREE(subreq);
	state->mdscli_ctx->async_pending--;
	if (tevent_req_nterror(req, status)) {
		return;
	}

	share_path_len = strlen(mdscli_ctx->mdscmd_open.share_path);
	if (share_path_len < 1 ||
	    share_path_len >= sizeof(mdscli_ctx->mdscmd_open.share_path))
	{
		tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
		return;
	}
	mdscli_ctx->mdscmd_open.share_path_len = share_path_len;

	if (mdscli_ctx->mdscmd_open.share_path[share_path_len-1] == '/') {
		mdscli_ctx->mdscmd_open.share_path[share_path_len-1] = '\0';
		mdscli_ctx->mdscmd_open.share_path_len--;
	}

	subreq = dcerpc_mdssvc_unknown1_send(
			state,
			state->ev,
			mdscli_ctx->bh,
			&mdscli_ctx->ph,
			0,
			mdscli_ctx->dev,
			mdscli_ctx->mdscmd_open.unkn2,
			0,
			geteuid(),
			getegid(),
			&mdscli_ctx->mdscmd_unknown1.status,
			&mdscli_ctx->flags,
			&mdscli_ctx->mdscmd_unknown1.unkn7);
	if (tevent_req_nomem(subreq, req)) {
		return;
	}
	tevent_req_set_callback(subreq, mdscli_connect_unknown1_done, req);
}

static void mdscli_connect_unknown1_done(struct tevent_req *subreq)
{
	struct tevent_req *req = tevent_req_callback_data(
		subreq, struct tevent_req);
	struct mdscli_connect_state *state = tevent_req_data(
		req, struct mdscli_connect_state);
	struct mdscli_ctx *mdscli_ctx = state->mdscli_ctx;
	struct mdssvc_blob request_blob;
	NTSTATUS status;

	status = dcerpc_mdssvc_unknown1_recv(subreq, state);
	TALLOC_FREE(subreq);
	if (tevent_req_nterror(req, status)) {
		return;
	}

	status = mdscli_blob_fetch_props(state,
					 state->mdscli_ctx,
					 &request_blob);
	if (tevent_req_nterror(req, status)) {
		return;
	}

	subreq = dcerpc_mdssvc_cmd_send(state,
					state->ev,
					mdscli_ctx->bh,
					&mdscli_ctx->ph,
					0,
					mdscli_ctx->dev,
					mdscli_ctx->mdscmd_open.unkn2,
					0,
					mdscli_ctx->flags,
					request_blob,
					0,
					mdscli_ctx->max_fragment_size,
					1,
					mdscli_ctx->max_fragment_size,
					0,
					0,
					&mdscli_ctx->mdscmd_cmd.fragment,
					&state->response_blob,
					&mdscli_ctx->mdscmd_cmd.unkn9);
	if (tevent_req_nomem(subreq, req)) {
		return;
	}
	tevent_req_set_callback(subreq, mdscli_connect_fetch_props_done, req);
	mdscli_ctx->async_pending++;
	return;
}

static void mdscli_connect_fetch_props_done(struct tevent_req *subreq)
{
	struct tevent_req *req = tevent_req_callback_data(
		subreq, struct tevent_req);
	struct mdscli_connect_state *state = tevent_req_data(
		req, struct mdscli_connect_state);
	struct mdscli_ctx *mdscli_ctx = state->mdscli_ctx;
	DALLOC_CTX *d = NULL;
	sl_array_t *path_scope_array = NULL;
	char *path_scope = NULL;
	NTSTATUS status;
	bool ok;

	status = dcerpc_mdssvc_cmd_recv(subreq, state);
	TALLOC_FREE(subreq);
	state->mdscli_ctx->async_pending--;
	if (tevent_req_nterror(req, status)) {
		return;
	}

	d = dalloc_new(state);
	if (tevent_req_nomem(d, req)) {
		return;
	}

	ok = sl_unpack(d,
		       (char *)state->response_blob.spotlight_blob,
		       state->response_blob.length);
	if (!ok) {
		tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
		return;
	}

	path_scope_array = dalloc_value_for_key(d,
						"DALLOC_CTX", 0,
						"kMDSStorePathScopes",
						"sl_array_t");
	if (path_scope_array == NULL) {
		DBG_ERR("Missing kMDSStorePathScopes\n");
		tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
		return;
	}

	path_scope = dalloc_get(path_scope_array, "char *", 0);
	if (path_scope == NULL) {
		DBG_ERR("Missing path in kMDSStorePathScopes\n");
		tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
		return;
	}

	mdscli_ctx->path_scope_len = strlen(path_scope);
	if (mdscli_ctx->path_scope_len < 1 ||
	    mdscli_ctx->path_scope_len > UINT16_MAX)
	{
		DBG_ERR("Bad path_scope: %s\n", path_scope);
		tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
		return;
	}
	mdscli_ctx->path_scope = talloc_strdup(mdscli_ctx, path_scope);
	if (tevent_req_nomem(mdscli_ctx->path_scope, req)) {
		return;
	}

	if (mdscli_ctx->path_scope[mdscli_ctx->path_scope_len-1] == '/') {
		mdscli_ctx->path_scope[mdscli_ctx->path_scope_len-1] = '\0';
		mdscli_ctx->path_scope_len--;
	}

	tevent_req_done(req);
}

NTSTATUS mdscli_connect_recv(struct tevent_req *req,
			     TALLOC_CTX *mem_ctx,
			     struct mdscli_ctx **mdscli_ctx)
{
	struct mdscli_connect_state *state = tevent_req_data(
		req, struct mdscli_connect_state);
	NTSTATUS status;

	if (tevent_req_is_nterror(req, &status)) {
		tevent_req_received(req);
		return status;
	}

	*mdscli_ctx = talloc_move(mem_ctx, &state->mdscli_ctx);
	tevent_req_received(req);
	return NT_STATUS_OK;
}

NTSTATUS mdscli_connect(TALLOC_CTX *mem_ctx,
			struct dcerpc_binding_handle *bh,
			const char *share_name,
			const char *mount_path,
			struct mdscli_ctx **mdscli_ctx)
{
	TALLOC_CTX *frame = talloc_stackframe();
	struct tevent_req *req = NULL;
	struct tevent_context *ev = NULL;
	NTSTATUS status = NT_STATUS_NO_MEMORY;

	ev = samba_tevent_context_init(frame);
	if (ev == NULL) {
		goto fail;
	}

	req = mdscli_connect_send(frame,
				  ev,
				  bh,
				  share_name,
				  mount_path);
	if (req == NULL) {
		goto fail;
	}
	if (!tevent_req_poll_ntstatus(req, ev, &status)) {
		goto fail;
	}

	status = mdscli_connect_recv(req, mem_ctx, mdscli_ctx);
fail:
	TALLOC_FREE(frame);
	return status;
}

struct mdscli_search_state {
	struct mdscli_search_ctx *search;
	struct mdssvc_blob request_blob;
	struct mdssvc_blob response_blob;
};

static void mdscli_search_cmd_done(struct tevent_req *subreq);

struct tevent_req *mdscli_search_send(TALLOC_CTX *mem_ctx,
				      struct tevent_context *ev,
				      struct mdscli_ctx *mdscli_ctx,
				      const char *mds_query,
				      const char *path_scope_in,
				      bool live)
{
	struct tevent_req *req = NULL;
	struct mdscli_search_state *state = NULL;
	struct tevent_req *subreq = NULL;
	struct mdscli_search_ctx *search = NULL;
	char *path_scope = NULL;
	NTSTATUS status;

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

	search = talloc_zero(state, struct mdscli_search_ctx);
	if (tevent_req_nomem(search, req)) {
		return tevent_req_post(req, ev);
	}

	if (path_scope_in[0] == '/') {
		path_scope = talloc_strdup(search, path_scope_in);
	} else {
		path_scope = talloc_asprintf(search,
					     "%s/%s",
					     mdscli_ctx->mdscmd_open.share_path,
					     path_scope_in);
	}
	if (tevent_req_nomem(path_scope, req)) {
		return tevent_req_post(req, ev);
	}

	*search = (struct mdscli_search_ctx) {
		.mdscli_ctx = mdscli_ctx,
		.ctx_id = mdscli_new_ctx_id(mdscli_ctx),
		.unique_id = generate_random_u64(),
		.live = live,
		.path_scope = path_scope,
		.mds_query = talloc_strdup(search, mds_query),
	};
	if (tevent_req_nomem(search->mds_query, req)) {
		return tevent_req_post(req, ev);
	}

	*state = (struct mdscli_search_state) {
		.search = search,
	};

	status = mdscli_blob_search(state,
				    search,
				    &state->request_blob);
	if (tevent_req_nterror(req, status)) {
		return tevent_req_post(req, ev);
	}

	subreq = dcerpc_mdssvc_cmd_send(state,
					ev,
					mdscli_ctx->bh,
					&mdscli_ctx->ph,
					0,
					mdscli_ctx->dev,
					mdscli_ctx->mdscmd_open.unkn2,
					0,
					mdscli_ctx->flags,
					state->request_blob,
					0,
					mdscli_ctx->max_fragment_size,
					1,
					mdscli_ctx->max_fragment_size,
					0,
					0,
					&mdscli_ctx->mdscmd_cmd.fragment,
					&state->response_blob,
					&mdscli_ctx->mdscmd_cmd.unkn9);
	if (tevent_req_nomem(subreq, req)) {
		return tevent_req_post(req, ev);
	}
	tevent_req_set_callback(subreq, mdscli_search_cmd_done, req);
	mdscli_ctx->async_pending++;
	return req;
}

static void mdscli_search_cmd_done(struct tevent_req *subreq)
{
	struct tevent_req *req = tevent_req_callback_data(
		subreq, struct tevent_req);
	struct mdscli_search_state *state = tevent_req_data(
		req, struct mdscli_search_state);
	DALLOC_CTX *d = NULL;
	uint64_t *uint64p = NULL;
	NTSTATUS status;
	bool ok;

	status = dcerpc_mdssvc_cmd_recv(subreq, state);
	TALLOC_FREE(subreq);
	state->search->mdscli_ctx->async_pending--;
	if (tevent_req_nterror(req, status)) {
		return;
	}

	d = dalloc_new(state);
	if (tevent_req_nomem(d, req)) {
		return;
	}

	ok = sl_unpack(d,
		       (char *)state->response_blob.spotlight_blob,
		       state->response_blob.length);
	if (!ok) {
		tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
		return;
	}

	uint64p = dalloc_get(d,
			     "DALLOC_CTX", 0,
			     "uint64_t", 0);
	if (uint64p == NULL) {
		DBG_DEBUG("Unexpected mds response: %s", dalloc_dump(d, 0));
		tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
		return;
	}

	if (*uint64p != 0) {
		DBG_DEBUG("Unexpected mds result: 0x%" PRIx64 "\n", *uint64p);
		tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
		return;
	}

	tevent_req_done(req);
	return;
}

NTSTATUS mdscli_search_recv(struct tevent_req *req,
			    TALLOC_CTX *mem_ctx,
			    struct mdscli_search_ctx **search)
{
	struct mdscli_search_state *state = tevent_req_data(
		req, struct mdscli_search_state);
	NTSTATUS status;

	if (tevent_req_is_nterror(req, &status)) {
		tevent_req_received(req);
		return status;
	}

	*search = talloc_move(mem_ctx, &state->search);
	tevent_req_received(req);
	return NT_STATUS_OK;
}

NTSTATUS mdscli_search(TALLOC_CTX *mem_ctx,
		       struct mdscli_ctx *mdscli_ctx,
		       const char *mds_query,
		       const char *path_scope,
		       bool live,
		       struct mdscli_search_ctx **search)
{
	TALLOC_CTX *frame = talloc_stackframe();
	struct tevent_req *req = NULL;
	struct tevent_context *ev = NULL;
	NTSTATUS status = NT_STATUS_NO_MEMORY;

	if (mdscli_ctx->async_pending != 0) {
		status = NT_STATUS_INVALID_PARAMETER;
		goto fail;
	}

	ev = samba_tevent_context_init(frame);
	if (ev == NULL) {
		goto fail;
	}

	req = mdscli_search_send(frame,
				 ev,
				 mdscli_ctx,
				 mds_query,
				 path_scope,
				 live);
	if (req == NULL) {
		goto fail;
	}
	if (!tevent_req_poll_ntstatus(req, ev, &status)) {
		goto fail;
	}

	status = mdscli_search_recv(req, mem_ctx, search);
fail:
	TALLOC_FREE(frame);
	return status;
}

struct mdscli_get_results_state {
	struct tevent_context *ev;
	struct mdscli_search_ctx *search;
	struct mdssvc_blob request_blob;
	struct mdssvc_blob response_fragment;
	DATA_BLOB response_data;
	uint64_t *cnids;
	uint32_t fragment;
};

static void mdscli_get_results_cmd_done(struct tevent_req *subreq);

struct tevent_req *mdscli_get_results_send(
			TALLOC_CTX *mem_ctx,
			struct tevent_context *ev,
			struct mdscli_search_ctx *search)
{
	struct tevent_req *req = NULL;
	struct mdscli_get_results_state *state = NULL;
	struct tevent_req *subreq = NULL;
	struct mdscli_ctx *mdscli_ctx = search->mdscli_ctx;
	NTSTATUS status;

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

	*state = (struct mdscli_get_results_state) {
		.ev = ev,
		.search = search,
	};

	status = mdscli_blob_get_results(state,
					 search,
					 &state->request_blob);
	if (tevent_req_nterror(req, status)) {
		return tevent_req_post(req, ev);
	}

	subreq = dcerpc_mdssvc_cmd_send(state,
					ev,
					mdscli_ctx->bh,
					&mdscli_ctx->ph,
					0,
					mdscli_ctx->dev,
					mdscli_ctx->mdscmd_open.unkn2,
					0,
					mdscli_ctx->flags,
					state->request_blob,
					0,
					mdscli_ctx->max_fragment_size,
					1,
					mdscli_ctx->max_fragment_size,
					0,
					0,
					&state->fragment,
					&state->response_fragment,
					&mdscli_ctx->mdscmd_cmd.unkn9);
	if (tevent_req_nomem(subreq, req)) {
		return tevent_req_post(req, ev);
	}
	tevent_req_set_callback(subreq, mdscli_get_results_cmd_done, req);
	mdscli_ctx->async_pending++;
	return req;
}

static void mdscli_get_results_cmd_done(struct tevent_req *subreq)
{
	struct tevent_req *req = tevent_req_callback_data(
		subreq, struct tevent_req);
	struct mdscli_get_results_state *state = tevent_req_data(
		req, struct mdscli_get_results_state);
	struct mdscli_ctx *mdscli_ctx = state->search->mdscli_ctx;
	size_t oldsize, newsize;
	DALLOC_CTX *d = NULL;
	uint64_t *uint64p = NULL;
	bool search_in_progress = false;
	sl_cnids_t *cnids = NULL;
	size_t ncnids;
	size_t i;
	NTSTATUS status;
	bool ok;

	status = dcerpc_mdssvc_cmd_recv(subreq, state);
	TALLOC_FREE(subreq);
	state->search->mdscli_ctx->async_pending--;
	if (tevent_req_nterror(req, status)) {
		return;
	}

	oldsize = state->response_data.length;
	newsize = oldsize + state->response_fragment.length;
	if (newsize < oldsize) {
		tevent_req_nterror(req, NT_STATUS_INTEGER_OVERFLOW);
		return;
	}

	ok = data_blob_realloc(state, &state->response_data, newsize);
	if (!ok) {
		tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
		return;
	}
	(void)memcpy(state->response_data.data + oldsize,
		     state->response_fragment.spotlight_blob,
		     state->response_fragment.length);

	TALLOC_FREE(state->response_fragment.spotlight_blob);
	state->response_fragment.length = 0;
	state->response_fragment.size = 0;

	if (state->fragment != 0) {
		subreq = dcerpc_mdssvc_cmd_send(
				state,
				state->ev,
				mdscli_ctx->bh,
				&mdscli_ctx->ph,
				0,
				mdscli_ctx->dev,
				mdscli_ctx->mdscmd_open.unkn2,
				1,
				mdscli_ctx->flags,
				state->request_blob,
				0,
				mdscli_ctx->max_fragment_size,
				1,
				mdscli_ctx->max_fragment_size,
				0,
				0,
				&state->fragment,
				&state->response_fragment,
				&mdscli_ctx->mdscmd_cmd.unkn9);
		if (tevent_req_nomem(subreq, req)) {
			tevent_req_post(req, state->ev);
			return;
		}
		tevent_req_set_callback(subreq,
					mdscli_get_results_cmd_done,
					req);
		mdscli_ctx->async_pending++;
		return;
	}

	d = dalloc_new(state);
	if (tevent_req_nomem(d, req)) {
		return;
	}

	ok = sl_unpack(d,
		       (char *)state->response_data.data,
		       state->response_data.length);
	if (!ok) {
		tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
		return;
	}

	uint64p = dalloc_get(d,
			     "DALLOC_CTX", 0,
			     "uint64_t", 0);
	if (uint64p == NULL) {
		DBG_DEBUG("Unexpected mds response: %s", dalloc_dump(d, 0));
		tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
		return;
	}

	if (*uint64p == 35) {
		DBG_DEBUG("Search in progress\n");
		search_in_progress = true;
	}

	cnids = dalloc_get(d, "DALLOC_CTX", 0, "sl_cnids_t", 1);
	if (cnids == NULL) {
		DBG_DEBUG("cnids error: %s", dalloc_dump(d, 0));
		tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
		return;
	}

	ncnids = dalloc_size(cnids->ca_cnids);
	if (ncnids == 0 && !search_in_progress) {
		tevent_req_nterror(req, NT_STATUS_NO_MORE_MATCHES);
		return;
	}

	if (cnids->ca_unkn1 != 0xadd) {
		/*
		 * Whatever 0xadd means... but it seems to be the standard value
		 * macOS mdssvc returns here.
		 */
		DBG_DEBUG("unexpected ca_unkn1: %s", dalloc_dump(d, 0));
		tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
		return;
	}

	if (cnids->ca_context != state->search->ctx_id.connection ) {
		DBG_DEBUG("unexpected ca_context: %s", dalloc_dump(d, 0));
		tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
		return;
	}

	state->cnids = talloc_zero_array(state, uint64_t, ncnids);
	if (tevent_req_nomem(state->cnids, req)) {
		return;
	}

	for (i = 0; i < ncnids; i++) {
		uint64_t *cnid = NULL;

		cnid = dalloc_get(cnids->ca_cnids, "uint64_t", i);
		if (cnid == NULL) {
			DBG_DEBUG("cnids error: %s", dalloc_dump(d, 0));
			tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
			return;
		}
		state->cnids[i] = *cnid;
	}

	tevent_req_done(req);
	return;
}

NTSTATUS mdscli_get_results_recv(struct tevent_req *req,
				 TALLOC_CTX *mem_ctx,
				 uint64_t **cnids)
{
	struct mdscli_get_results_state *state = tevent_req_data(
		req, struct mdscli_get_results_state);
	NTSTATUS status;

	if (tevent_req_is_nterror(req, &status)) {
		tevent_req_received(req);
		return status;
	}

	*cnids = talloc_move(mem_ctx, &state->cnids);

	tevent_req_received(req);
	return NT_STATUS_OK;
}

NTSTATUS mdscli_get_results(TALLOC_CTX *mem_ctx,
			    struct mdscli_search_ctx *search,
			    uint64_t **_cnids)
{
	TALLOC_CTX *frame = talloc_stackframe();
	struct tevent_req *req = NULL;
	struct tevent_context *ev = NULL;
	NTSTATUS status = NT_STATUS_NO_MEMORY;

	if (search->mdscli_ctx->async_pending != 0) {
		status = NT_STATUS_INVALID_PARAMETER;
		goto fail;
	}

	ev = samba_tevent_context_init(frame);
	if (ev == NULL) {
		goto fail;
	}

	req = mdscli_get_results_send(frame,
				      ev,
				      search);
	if (req == NULL) {
		goto fail;
	}
	if (!tevent_req_poll_ntstatus(req, ev, &status)) {
		goto fail;
	}

	status = mdscli_get_results_recv(req, mem_ctx, _cnids);
fail:
	TALLOC_FREE(frame);
	return status;
}

struct mdscli_get_path_state {
	struct mdscli_ctx *mdscli_ctx;
	struct mdssvc_blob request_blob;
	struct mdssvc_blob response_blob;
	char *path;
};

static void mdscli_get_path_done(struct tevent_req *subreq);

struct tevent_req *mdscli_get_path_send(TALLOC_CTX *mem_ctx,
					struct tevent_context *ev,
					struct mdscli_ctx *mdscli_ctx,
					uint64_t cnid)
{
	struct tevent_req *req = NULL;
	struct mdscli_get_path_state *state = NULL;
	struct tevent_req *subreq = NULL;
	NTSTATUS status;

	req = tevent_req_create(req, &state, struct mdscli_get_path_state);
	if (req == NULL) {
		return NULL;
	}
	*state = (struct mdscli_get_path_state) {
		.mdscli_ctx = mdscli_ctx,
	};

	status = mdscli_blob_get_path(state,
				      mdscli_ctx,
				      cnid,
				      &state->request_blob);
	if (tevent_req_nterror(req, status)) {
		return tevent_req_post(req, ev);
	}

	subreq = dcerpc_mdssvc_cmd_send(state,
					ev,
					mdscli_ctx->bh,
					&mdscli_ctx->ph,
					0,
					mdscli_ctx->dev,
					mdscli_ctx->mdscmd_open.unkn2,
					0,
					mdscli_ctx->flags,
					state->request_blob,
					0,
					mdscli_ctx->max_fragment_size,
					1,
					mdscli_ctx->max_fragment_size,
					0,
					0,
					&mdscli_ctx->mdscmd_cmd.fragment,
					&state->response_blob,
					&mdscli_ctx->mdscmd_cmd.unkn9);
	if (tevent_req_nomem(subreq, req)) {
		return tevent_req_post(req, ev);
	}
	tevent_req_set_callback(subreq, mdscli_get_path_done, req);
	mdscli_ctx->async_pending++;
	return req;
}

static void mdscli_get_path_done(struct tevent_req *subreq)
{
	struct tevent_req *req = tevent_req_callback_data(
		subreq, struct tevent_req);
	struct mdscli_get_path_state *state = tevent_req_data(
		req, struct mdscli_get_path_state);
	DALLOC_CTX *d = NULL;
	size_t pathlen;
	size_t prefixlen;
	char *path = NULL;
	const char *p = NULL;
	NTSTATUS status;
	bool ok;

	status = dcerpc_mdssvc_cmd_recv(subreq, state);
	TALLOC_FREE(subreq);
	state->mdscli_ctx->async_pending--;
	if (tevent_req_nterror(req, status)) {
		return;
	}

	d = dalloc_new(state);
	if (tevent_req_nomem(d, req)) {
		return;
	}

	ok = sl_unpack(d,
		       (char *)state->response_blob.spotlight_blob,
		       state->response_blob.length);
	if (!ok) {
		tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
		return;
	}

	path = dalloc_get(d,
			  "DALLOC_CTX", 0,
			  "DALLOC_CTX", 2,
			  "DALLOC_CTX", 0,
			  "DALLOC_CTX", 1,
			  "char *", 0);
	if (path == NULL) {
		DBG_DEBUG("No path in mds response: %s", dalloc_dump(d, 0));
		tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
		return;
	}

	/* Path is prefixed by /PATHSCOPE/SHARENAME/, strip it */
	pathlen = strlen(path);

	/*
	 * path_scope_len and share_path_len are already checked to be smaller
	 * then UINT16_MAX so this can't overflow
	 */
	prefixlen = state->mdscli_ctx->path_scope_len
		+ state->mdscli_ctx->mdscmd_open.share_path_len;

	if (pathlen < prefixlen) {
		DBG_DEBUG("Bad path: %s\n", path);
		tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
		return;
	}

	p = path + prefixlen;
	while (*p == '/') {
		p++;
	}
	if (*p == '\0') {
		DBG_DEBUG("Bad path: %s\n", path);
		tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
		return;
	}

	state->path = talloc_strdup(state, p);
	if (state->path == NULL) {
		tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
		return;
	}
	DBG_DEBUG("path: %s\n", state->path);

	tevent_req_done(req);
	return;
}

NTSTATUS mdscli_get_path_recv(struct tevent_req *req,
			      TALLOC_CTX *mem_ctx,
			      char **path)
{
	struct mdscli_get_path_state *state = tevent_req_data(
		req, struct mdscli_get_path_state);
	NTSTATUS status;

	if (tevent_req_is_nterror(req, &status)) {
		tevent_req_received(req);
		return status;
	}

	*path = talloc_move(mem_ctx, &state->path);
	tevent_req_received(req);
	return NT_STATUS_OK;
}

NTSTATUS mdscli_get_path(TALLOC_CTX *mem_ctx,
			 struct mdscli_ctx *mdscli_ctx,
			 uint64_t cnid,
			 char **path)
{
	TALLOC_CTX *frame = talloc_stackframe();
	struct tevent_req *req = NULL;
	struct tevent_context *ev = NULL;
	NTSTATUS status = NT_STATUS_NO_MEMORY;

	if (mdscli_ctx->async_pending != 0) {
		status = NT_STATUS_INVALID_PARAMETER;
		goto fail;
	}

	ev = samba_tevent_context_init(frame);
	if (ev == NULL) {
		goto fail;
	}

	req = mdscli_get_path_send(frame, ev, mdscli_ctx, cnid);
	if (req == NULL) {
		goto fail;
	}
	if (!tevent_req_poll_ntstatus(req, ev, &status)) {
		goto fail;
	}

	status = mdscli_get_path_recv(req, mem_ctx, path);
fail:
	TALLOC_FREE(frame);
	return status;
}

struct mdscli_close_search_state {
	struct mdscli_search_ctx *search;
	struct mdssvc_blob request_blob;
	struct mdssvc_blob response_blob;
};

static void mdscli_close_search_done(struct tevent_req *subreq);

struct tevent_req *mdscli_close_search_send(TALLOC_CTX *mem_ctx,
					    struct tevent_context *ev,
					    struct mdscli_search_ctx **search)
{
	struct mdscli_ctx *mdscli_ctx = NULL;
	struct tevent_req *req = NULL;
	struct mdscli_close_search_state *state = NULL;
	struct tevent_req *subreq = NULL;
	NTSTATUS status;

	req = tevent_req_create(req, &state, struct mdscli_close_search_state);
	if (req == NULL) {
		return NULL;
	}
	*state = (struct mdscli_close_search_state) {
		.search = talloc_move(state, search),
	};
	mdscli_ctx = state->search->mdscli_ctx;

	status = mdscli_blob_close_search(state,
					  state->search,
					  &state->request_blob);
	if (tevent_req_nterror(req, status)) {
		return tevent_req_post(req, ev);
	}

	subreq = dcerpc_mdssvc_cmd_send(state,
					ev,
					mdscli_ctx->bh,
					&mdscli_ctx->ph,
					0,
					mdscli_ctx->dev,
					mdscli_ctx->mdscmd_open.unkn2,
					0,
					mdscli_ctx->flags,
					state->request_blob,
					0,
					mdscli_ctx->max_fragment_size,
					1,
					mdscli_ctx->max_fragment_size,
					0,
					0,
					&mdscli_ctx->mdscmd_cmd.fragment,
					&state->response_blob,
					&mdscli_ctx->mdscmd_cmd.unkn9);
	if (tevent_req_nomem(subreq, req)) {
		return tevent_req_post(req, ev);
	}
	tevent_req_set_callback(subreq, mdscli_close_search_done, req);
	mdscli_ctx->async_pending++;
	return req;
}

static void mdscli_close_search_done(struct tevent_req *subreq)
{
	struct tevent_req *req = tevent_req_callback_data(
		subreq, struct tevent_req);
	struct mdscli_close_search_state *state = tevent_req_data(
		req, struct mdscli_close_search_state);
	NTSTATUS status;

	status = dcerpc_mdssvc_cmd_recv(subreq, state);
	TALLOC_FREE(subreq);
	state->search->mdscli_ctx->async_pending--;
	if (tevent_req_nterror(req, status)) {
		return;
	}

	tevent_req_done(req);
	return;
}

NTSTATUS mdscli_close_search_recv(struct tevent_req *req)
{
	return tevent_req_simple_recv_ntstatus(req);
}

NTSTATUS mdscli_close_search(struct mdscli_search_ctx **search)
{
	TALLOC_CTX *frame = talloc_stackframe();
	struct tevent_req *req = NULL;
	struct tevent_context *ev = NULL;
	NTSTATUS status = NT_STATUS_NO_MEMORY;

	if ((*search)->mdscli_ctx->async_pending != 0) {
		status = NT_STATUS_INVALID_PARAMETER;
		goto fail;
	}

	ev = samba_tevent_context_init(frame);
	if (ev == NULL) {
		goto fail;
	}

	req = mdscli_close_search_send(frame, ev, search);
	if (req == NULL) {
		goto fail;
	}
	if (!tevent_req_poll_ntstatus(req, ev, &status)) {
		goto fail;
	}

	status = mdscli_close_search_recv(req);
fail:
	TALLOC_FREE(frame);
	return status;
}

struct mdscli_disconnect_state {
	struct mdscli_ctx *mdscli_ctx;
};

static void mdscli_disconnect_done(struct tevent_req *subreq);

struct tevent_req *mdscli_disconnect_send(TALLOC_CTX *mem_ctx,
					  struct tevent_context *ev,
					  struct mdscli_ctx *mdscli_ctx)
{
	struct tevent_req *req = NULL;
	struct mdscli_disconnect_state *state = NULL;
	struct tevent_req *subreq = NULL;

	req = tevent_req_create(req, &state, struct mdscli_disconnect_state);
	if (req == NULL) {
		return NULL;
	}
	*state = (struct mdscli_disconnect_state) {
		.mdscli_ctx = mdscli_ctx,
	};

	subreq = dcerpc_mdssvc_close_send(state,
					  ev,
					  mdscli_ctx->bh,
					  &mdscli_ctx->ph,
					  0,
					  mdscli_ctx->dev,
					  mdscli_ctx->mdscmd_open.unkn2,
					  0,
					  &mdscli_ctx->ph,
					  &mdscli_ctx->mdscmd_close.status);
	if (tevent_req_nomem(subreq, req)) {
		return tevent_req_post(req, ev);
	}
	tevent_req_set_callback(subreq, mdscli_disconnect_done, req);
	mdscli_ctx->async_pending++;
	return req;
}

static void mdscli_disconnect_done(struct tevent_req *subreq)
{
	struct tevent_req *req = tevent_req_callback_data(
		subreq, struct tevent_req);
	struct mdscli_disconnect_state *state = tevent_req_data(
		req, struct mdscli_disconnect_state);
	NTSTATUS status;

	status = dcerpc_mdssvc_close_recv(subreq, state);
	TALLOC_FREE(subreq);
	state->mdscli_ctx->async_pending--;
	if (tevent_req_nterror(req, status)) {
		return;
	}

	tevent_req_done(req);
	return;
}

NTSTATUS mdscli_disconnect_recv(struct tevent_req *req)
{
	return tevent_req_simple_recv_ntstatus(req);
}

NTSTATUS mdscli_disconnect(struct mdscli_ctx *mdscli_ctx)
{
	TALLOC_CTX *frame = talloc_stackframe();
	struct tevent_req *req = NULL;
	struct tevent_context *ev = NULL;
	NTSTATUS status = NT_STATUS_NO_MEMORY;

	if (mdscli_ctx->async_pending != 0) {
		status = NT_STATUS_INVALID_PARAMETER;
		goto fail;
	}

	ev = samba_tevent_context_init(frame);
	if (ev == NULL) {
		goto fail;
	}

	req = mdscli_disconnect_send(frame, ev, mdscli_ctx);
	if (req == NULL) {
		goto fail;
	}
	if (!tevent_req_poll_ntstatus(req, ev, &status)) {
		goto fail;
	}

	status = mdscli_disconnect_recv(req);
fail:
	TALLOC_FREE(frame);
	return status;
}
