/*
   Unix SMB/CIFS implementation.
   RPC Spotlight client

   Copyright (C) Ralph Boehme 2018

   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 "rpcclient.h"
#include "libsmb/libsmb.h"
#include "../librpc/gen_ndr/ndr_mdssvc_c.h"
#include "../rpc_server/mdssvc/mdssvc.h"
#include "../rpc_server/mdssvc/dalloc.h"
#include "../rpc_server/mdssvc/marshalling.h"

static NTSTATUS cmd_mdssvc_fetch_properties(
			struct rpc_pipe_client *cli,
			TALLOC_CTX *mem_ctx,
			int argc, const char **argv)
{
	struct dcerpc_binding_handle *b = cli->binding_handle;
	NTSTATUS status;
	uint32_t device_id = 0x2f000045;
	uint32_t unkn1 = 23;
	uint32_t unkn2 = 0;
	struct policy_handle share_handle;
	char share_path[1025] = { 0 };
	uint32_t mds_status;
	uint32_t flags;	     /* server always returns 0x6b000001 ? */
	uint32_t unkn3;	     /* server always returns 0 ? */
	struct mdssvc_blob request_blob;
	struct mdssvc_blob response_blob;
	uint32_t max_fragment_size = 64 * 1024;
	DALLOC_CTX *d, *mds_reply;
	uint64_t *uint64var;
	sl_array_t *array1, *array2;
	uint32_t unkn4;
	int result;
	bool ok;

	if (argc != 3) {
		printf("Usage: %s SHARENAME MOUNTPATH\n", argv[0]);
		return NT_STATUS_OK;
	}

	status = dcerpc_mdssvc_open(b, mem_ctx,
				    &device_id,
				    &unkn1,
				    &unkn2,
				    argv[2],
				    argv[1],
				    share_path,
				    &share_handle);
	if (!NT_STATUS_IS_OK(status)) {
		goto done;
	}

	status = dcerpc_mdssvc_unknown1(b, mem_ctx,
					&share_handle,
					0,
					device_id,
					unkn1,
					0,
					geteuid(),
					getegid(),
					&mds_status,
					&flags,
					&unkn3);
	if (!NT_STATUS_IS_OK(status)) {
		goto done;
	}

	d = dalloc_new(mem_ctx);
	if (d == NULL) {
		status = NT_STATUS_NO_MEMORY;
		goto done;
	}

	mds_reply = dalloc_new(mem_ctx);
	if (mds_reply == NULL) {
		status = NT_STATUS_NO_MEMORY;
		goto done;
	}

	array1 = dalloc_zero(d, sl_array_t);
	if (array1 == NULL) {
		status = NT_STATUS_NO_MEMORY;
		goto done;
	}

	array2 = dalloc_zero(d, sl_array_t);
	if (array2 == NULL) {
		status = NT_STATUS_NO_MEMORY;
		goto done;
	}

	result = dalloc_stradd(array2, "fetchPropertiesForContext:");
	if (result != 0) {
		status = NT_STATUS_INTERNAL_ERROR;
		goto done;
	}
	uint64var = talloc_zero_array(mem_ctx, uint64_t, 2);
	if (uint64var == NULL) {
		status = NT_STATUS_NO_MEMORY;
		goto done;
	}
	talloc_set_name(uint64var, "uint64_t *");

	result = dalloc_add(array2, &uint64var[0], uint64_t *);
	if (result != 0) {
		status = NT_STATUS_INTERNAL_ERROR;
		goto done;
	}

	result = dalloc_add(array1, array2, sl_array_t);
	if (result != 0) {
		status = NT_STATUS_INTERNAL_ERROR;
		goto done;
	}
	result = dalloc_add(d, array1, sl_array_t);
	if (result != 0) {
		status = NT_STATUS_INTERNAL_ERROR;
		goto done;
	}

	status = sl_pack_alloc(mem_ctx, d, &request_blob, max_fragment_size);
	if (!NT_STATUS_IS_OK(status)) {
		goto done;
	}

	status =  dcerpc_mdssvc_cmd(b, mem_ctx,
				    &share_handle,
				    0,
				    device_id,
				    23,
				    0,
				    0x6b000001,
				    request_blob,
				    0,
				    max_fragment_size,
				    1,
				    max_fragment_size,
				    0,
				    0,
				    &mds_status,
				    &response_blob,
				    &unkn4);
	if (!NT_STATUS_IS_OK(status)) {
		goto done;
	}

	ok = sl_unpack(mds_reply, (char *)response_blob.spotlight_blob,
		       response_blob.length);
	if (!ok) {
		DEBUG(1, ("error unpacking Spotlight RPC blob\n"));
		status = NT_STATUS_INTERNAL_ERROR;
		goto done;
	}

	DEBUG(0, ("%s", dalloc_dump(mds_reply, 0)));

done:
	return status;
}

static NTSTATUS cmd_mdssvc_fetch_attributes(
			struct rpc_pipe_client *cli,
			TALLOC_CTX *mem_ctx,
			int argc, const char **argv)
{
	struct dcerpc_binding_handle *b = cli->binding_handle;
	NTSTATUS status;
	uint32_t device_id = 0x2f000045;
	uint32_t unkn1 = 23;
	uint32_t unkn2 = 0;
	struct policy_handle share_handle;
	char share_path[1025];
	uint32_t mds_status;
	uint32_t flags;	     /* server always returns 0x6b000001 ? */
	uint32_t unkn3;	     /* server always returns 0 ? */
	struct mdssvc_blob request_blob;
	struct mdssvc_blob response_blob;
	uint32_t max_fragment_size = 64 * 1024;
	DALLOC_CTX *d, *mds_reply;
	uint64_t *uint64var;
	sl_array_t *array;
	sl_array_t *cmd_array;
	sl_array_t *attr_array;
	sl_cnids_t *cnids;
	uint64_t cnid;
	uint32_t unkn4;
	int result;
	bool ok;

	if (argc != 4) {
		printf("Usage: %s SHARENAME MOUNTPATH CNID\n", argv[0]);
		return NT_STATUS_OK;
	}

	ok = conv_str_u64(argv[3], &cnid);
	if (!ok) {
		printf("Failed to parse: %s\n", argv[3]);
		return NT_STATUS_INVALID_PARAMETER;
	}

	status = dcerpc_mdssvc_open(b, mem_ctx,
				    &device_id,
				    &unkn1,
				    &unkn2,
				    argv[2],
				    argv[1],
				    share_path,
				    &share_handle);
	if (!NT_STATUS_IS_OK(status)) {
		goto done;
	}

	status = dcerpc_mdssvc_unknown1(b, mem_ctx,
					&share_handle,
					0,
					device_id,
					unkn1,
					0,
					geteuid(),
					getegid(),
					&mds_status,
					&flags,
					&unkn3);
	if (!NT_STATUS_IS_OK(status)) {
		goto done;
	}

	d = dalloc_new(mem_ctx);
	if (d == NULL) {
		status = NT_STATUS_NO_MEMORY;
		goto done;
	}

	array = dalloc_zero(d, sl_array_t);
	if (array == NULL) {
		status = NT_STATUS_NO_MEMORY;
		goto done;
	}

	result = dalloc_add(d, array, sl_array_t);
	if (result != 0) {
		status = NT_STATUS_INTERNAL_ERROR;
		goto done;
	}

	cmd_array = dalloc_zero(d, sl_array_t);
	if (cmd_array == NULL) {
		status = NT_STATUS_NO_MEMORY;
		goto done;
	}

	result = dalloc_add(array, cmd_array, sl_array_t);
	if (result != 0) {
		status = NT_STATUS_INTERNAL_ERROR;
		goto done;
	}

	result = dalloc_stradd(cmd_array,
			       "fetchAttributes:forOIDArray:context:");
	if (result != 0) {
		status = NT_STATUS_INTERNAL_ERROR;
		goto done;
	}

	uint64var = talloc_zero_array(mem_ctx, uint64_t, 2);
	if (uint64var == NULL) {
		status = NT_STATUS_NO_MEMORY;
		goto done;
	}
	talloc_set_name(uint64var, "uint64_t *");
	uint64var[0] = 0x500a;
	uint64var[1] = 0;

	result = dalloc_add(cmd_array, &uint64var[0], uint64_t *);
	if (result != 0) {
		status = NT_STATUS_INTERNAL_ERROR;
		goto done;
	}

	attr_array = dalloc_zero(d, sl_array_t);
	if (attr_array == NULL) {
		status = NT_STATUS_NO_MEMORY;
		goto done;
	}

	result = dalloc_add(array, attr_array, sl_array_t);
	if (result != 0) {
		status = NT_STATUS_INTERNAL_ERROR;
		goto done;
	}

	result = dalloc_stradd(attr_array, "kMDItemPath");
	if (result != 0) {
		status = NT_STATUS_INTERNAL_ERROR;
		goto done;
	}

	/* CNIDs */
	cnids = talloc_zero(array, sl_cnids_t);
	if (cnids == NULL) {
		status = NT_STATUS_INTERNAL_ERROR;
		goto done;
	}
	cnids->ca_cnids = dalloc_new(cnids);
	if (cnids->ca_cnids == NULL) {
		status = NT_STATUS_INTERNAL_ERROR;
		goto done;
	}

	cnids->ca_unkn1 = 0xadd;
	cnids->ca_context = 0x6b000020;

	result = dalloc_add_copy(cnids->ca_cnids, &cnid, uint64_t);
	if (result != 0) {
		status = NT_STATUS_INTERNAL_ERROR;
		goto done;
	}

	result = dalloc_add(array, cnids, sl_cnids_t);
	if (result != 0) {
		status = NT_STATUS_INTERNAL_ERROR;
		goto done;
	}

	status = sl_pack_alloc(mem_ctx, d, &request_blob, max_fragment_size);
	if (!NT_STATUS_IS_OK(status)) {
		goto done;
	}

	status = dcerpc_mdssvc_cmd(b, mem_ctx,
				   &share_handle,
				   0,
				   device_id,
				   23,
				   0,
				   0x6b000001,
				   request_blob,
				   0,
				   max_fragment_size,
				   1,
				   max_fragment_size,
				   0,
				   0,
				   &mds_status,
				   &response_blob,
				   &unkn4);
	if (!NT_STATUS_IS_OK(status)) {
		printf("dcerpc_mdssvc_cmd failed: %s\n", nt_errstr(status));
		goto done;
	}

	if (response_blob.length == 0) {
		printf("mdssvc returned empty response\n");
		status = NT_STATUS_RPC_PROTOCOL_ERROR;
		goto done;
	}

	mds_reply = dalloc_new(mem_ctx);
	if (mds_reply == NULL) {
		status = NT_STATUS_NO_MEMORY;
		goto done;
	}

	ok = sl_unpack(mds_reply, (char *)response_blob.spotlight_blob,
		       response_blob.length);
	if (!ok) {
		printf("Unpacking Spotlight RPC blob failed\n");
		status = NT_STATUS_INTERNAL_ERROR;
		goto done;
	}

	printf("%s", dalloc_dump(mds_reply, 0));

done:
	return status;
}

/* List of commands exported by this module */

struct cmd_set spotlight_commands[] = {

	{
		.name = "MDSSVC"
	},
	{
		.name = "fetch_properties",
		.returntype = RPC_RTYPE_NTSTATUS,
		.ntfn = cmd_mdssvc_fetch_properties,
		.table = &ndr_table_mdssvc,
		.description = "Fetch connection properties",
		.usage = "",
	},
	{
		.name = "fetch_attributes",
		.returntype = RPC_RTYPE_NTSTATUS,
		.ntfn = cmd_mdssvc_fetch_attributes,
		.table = &ndr_table_mdssvc,
		.description = "Fetch attributes for a CNID",
		.usage = "",
	},
	{0}
};