/*
   Unix SMB/CIFS implementation.

   Copyright (C) Ralph Boehme 2017

   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 "smbd/smbd.h"
#include "smbd/globals.h"
#include "../libcli/security/security.h"
#include "dbwrap/dbwrap.h"
#include "dbwrap/dbwrap_rbt.h"
#include "dbwrap/dbwrap_open.h"
#include "../lib/util/util_tdb.h"
#include "librpc/gen_ndr/ndr_ioctl.h"
#include "librpc/gen_ndr/ioctl.h"
#include "offload_token.h"

struct vfs_offload_ctx {
	bool initialized;
	struct db_context *db_ctx;
};

NTSTATUS vfs_offload_token_ctx_init(TALLOC_CTX *mem_ctx,
				    struct vfs_offload_ctx **_ctx)
{
	struct vfs_offload_ctx *ctx = *_ctx;

	if (ctx != NULL) {
		if (!ctx->initialized) {
			return NT_STATUS_INTERNAL_ERROR;
		}
		return NT_STATUS_OK;
	}

	ctx = talloc_zero(mem_ctx, struct vfs_offload_ctx);
	if (ctx == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	ctx->db_ctx = db_open_rbt(mem_ctx);
	if (ctx->db_ctx == NULL) {
		TALLOC_FREE(ctx);
		return NT_STATUS_INTERNAL_ERROR;
	}

	ctx->initialized = true;
	*_ctx = ctx;
	return NT_STATUS_OK;
}

struct fsp_token_link {
	struct vfs_offload_ctx *ctx;
	DATA_BLOB token_blob;
};

static int fsp_token_link_destructor(struct fsp_token_link *link)
{
	DATA_BLOB token_blob = link->token_blob;
	TDB_DATA key = make_tdb_data(token_blob.data, token_blob.length);
	NTSTATUS status;

	status = dbwrap_delete(link->ctx->db_ctx, key);
	if (!NT_STATUS_IS_OK(status)) {
		DBG_ERR("dbwrap_delete failed: %s. Token:\n", nt_errstr(status));
		dump_data(0, token_blob.data, token_blob.length);
		return -1;
	}

	return 0;
}

NTSTATUS vfs_offload_token_db_store_fsp(struct vfs_offload_ctx *ctx,
					const files_struct *fsp,
					const DATA_BLOB *token_blob)
{
	struct db_record *rec = NULL;
	struct fsp_token_link *link = NULL;
	TDB_DATA key = make_tdb_data(token_blob->data, token_blob->length);
	TDB_DATA value;
	NTSTATUS status;

	rec = dbwrap_fetch_locked(ctx->db_ctx, talloc_tos(), key);
	if (rec == NULL) {
		return NT_STATUS_INTERNAL_ERROR;
	}

	value = dbwrap_record_get_value(rec);
	if (value.dsize != 0) {
		void *ptr = NULL;
		files_struct *token_db_fsp = NULL;

		if (value.dsize != sizeof(ptr)) {
			DBG_ERR("Bad db entry for token:\n");
			dump_data(1, token_blob->data, token_blob->length);
			TALLOC_FREE(rec);
			return NT_STATUS_INTERNAL_ERROR;
		}
		memcpy(&ptr, value.dptr, value.dsize);
		TALLOC_FREE(rec);

		token_db_fsp = talloc_get_type_abort(ptr, struct files_struct);
		if (token_db_fsp != fsp) {
			DBG_ERR("token for fsp [%s] matches already known "
				"but different fsp [%s]:\n",
				fsp_str_dbg(fsp), fsp_str_dbg(token_db_fsp));
			dump_data(1, token_blob->data, token_blob->length);
			return NT_STATUS_INTERNAL_ERROR;
		}

		return NT_STATUS_OK;
	}

	link = talloc_zero(fsp, struct fsp_token_link);
	if (link == NULL) {
		return NT_STATUS_NO_MEMORY;
	}
	link->ctx = ctx;
	link->token_blob = data_blob_talloc(link, token_blob->data,
					    token_blob->length);
	if (link->token_blob.data == NULL) {
		TALLOC_FREE(link);
		return NT_STATUS_NO_MEMORY;
	}
	talloc_set_destructor(link, fsp_token_link_destructor);

	value = make_tdb_data((uint8_t *)&fsp, sizeof(files_struct *));

	status = dbwrap_record_store(rec, value, 0);
	if (!NT_STATUS_IS_OK(status)) {
		DBG_ERR("dbwrap_record_store for [%s] failed: %s. Token\n",
			fsp_str_dbg(fsp), nt_errstr(status));
		dump_data(0, token_blob->data, token_blob->length);
		TALLOC_FREE(link);
		TALLOC_FREE(rec);
		return status;
	}

	TALLOC_FREE(rec);
	return NT_STATUS_OK;
}

NTSTATUS vfs_offload_token_db_fetch_fsp(struct vfs_offload_ctx *ctx,
					const DATA_BLOB *token_blob,
					files_struct **fsp)
{
	struct db_record *rec = NULL;
	TDB_DATA key = make_tdb_data(token_blob->data, token_blob->length);
	TDB_DATA value;
	void *ptr = NULL;

	rec = dbwrap_fetch_locked(ctx->db_ctx, talloc_tos(), key);
	if (rec == NULL) {
		return NT_STATUS_INTERNAL_ERROR;
	}

	value = dbwrap_record_get_value(rec);
	if (value.dsize == 0) {
		DBG_DEBUG("Unknown token:\n");
		dump_data(10, token_blob->data, token_blob->length);
		TALLOC_FREE(rec);
		return NT_STATUS_OBJECT_NAME_NOT_FOUND;
	}

	if (value.dsize != sizeof(ptr)) {
		DBG_ERR("Bad db entry for token:\n");
		dump_data(1, token_blob->data, token_blob->length);
		TALLOC_FREE(rec);
		return NT_STATUS_INTERNAL_ERROR;
	}

	memcpy(&ptr, value.dptr, value.dsize);
	TALLOC_FREE(rec);

	*fsp = talloc_get_type_abort(ptr, struct files_struct);
	return NT_STATUS_OK;
}

NTSTATUS vfs_offload_token_create_blob(TALLOC_CTX *mem_ctx,
				       const files_struct *fsp,
				       uint32_t fsctl,
				       DATA_BLOB *token_blob)
{
	size_t len;

	switch (fsctl) {
	case FSCTL_DUP_EXTENTS_TO_FILE:
		len = 20;
		break;
	case FSCTL_SRV_REQUEST_RESUME_KEY:
		len = 24;
		break;
	default:
		DBG_ERR("Invalid fsctl [%" PRIu32 "]\n", fsctl);
		return NT_STATUS_NOT_SUPPORTED;
	}

	*token_blob = data_blob_talloc_zero(mem_ctx, len);
	if (token_blob->length == 0) {
		return NT_STATUS_NO_MEMORY;
	}

	/* combine persistent and volatile handles for the resume key */
	SBVAL(token_blob->data,  0, fsp->op->global->open_persistent_id);
	SBVAL(token_blob->data,  8, fsp->op->global->open_volatile_id);
	SIVAL(token_blob->data, 16, fsctl);

	return NT_STATUS_OK;
}


NTSTATUS vfs_offload_token_check_handles(uint32_t fsctl,
					 files_struct *src_fsp,
					 files_struct *dst_fsp)
{
	if (src_fsp->vuid != dst_fsp->vuid) {
		DBG_INFO("copy chunk handles not in the same session.\n");
		return NT_STATUS_ACCESS_DENIED;
	}

	if (!NT_STATUS_IS_OK(src_fsp->op->status)) {
		DBG_INFO("copy chunk source handle invalid: %s\n",
			 nt_errstr(src_fsp->op->status));
		return NT_STATUS_ACCESS_DENIED;
	}

	if (!NT_STATUS_IS_OK(dst_fsp->op->status)) {
		DBG_INFO("copy chunk destination handle invalid: %s\n",
			 nt_errstr(dst_fsp->op->status));
		return NT_STATUS_ACCESS_DENIED;
	}

	if (src_fsp->deferred_close != NULL) {
		DBG_INFO("copy chunk src handle with deferred close.\n");
		return NT_STATUS_ACCESS_DENIED;
	}

	if (dst_fsp->deferred_close != NULL) {
		DBG_INFO("copy chunk dst handle with deferred close.\n");
		return NT_STATUS_ACCESS_DENIED;
	}

	if (src_fsp->is_directory) {
		DBG_INFO("copy chunk no read on src directory handle (%s).\n",
			 smb_fname_str_dbg(src_fsp->fsp_name));
		return NT_STATUS_ACCESS_DENIED;
	}

	if (dst_fsp->is_directory) {
		DBG_INFO("copy chunk no read on dst directory handle (%s).\n",
			 smb_fname_str_dbg(dst_fsp->fsp_name));
		return NT_STATUS_ACCESS_DENIED;
	}

	if (IS_IPC(src_fsp->conn) || IS_IPC(dst_fsp->conn)) {
		DBG_INFO("copy chunk no access on IPC$ handle.\n");
		return NT_STATUS_ACCESS_DENIED;
	}

	if (IS_PRINT(src_fsp->conn) || IS_PRINT(dst_fsp->conn)) {
		DBG_INFO("copy chunk no access on PRINT handle.\n");
		return NT_STATUS_ACCESS_DENIED;
	}

	/*
	 * [MS-SMB2] 3.3.5.15.6 Handling a Server-Side Data Copy Request
	 * The server MUST fail the request with STATUS_ACCESS_DENIED if any of
	 * the following are true:
	 * - The Open.GrantedAccess of the destination file does not include
	 *   FILE_WRITE_DATA or FILE_APPEND_DATA.
	 *
	 * A non writable dst handle also doesn't make sense for other fsctls.
	 */
	if (!CHECK_WRITE(dst_fsp)) {
		DBG_INFO("dest handle not writable (%s).\n",
			smb_fname_str_dbg(dst_fsp->fsp_name));
		return NT_STATUS_ACCESS_DENIED;
	}
	/*
	 * - The Open.GrantedAccess of the destination file does not include
	 *   FILE_READ_DATA, and the CtlCode is FSCTL_SRV_COPYCHUNK.
	 */
	if ((fsctl == FSCTL_SRV_COPYCHUNK) && !CHECK_READ_IOCTL(dst_fsp)) {
		DBG_INFO("copy chunk no read on dest handle (%s).\n",
			 smb_fname_str_dbg(dst_fsp->fsp_name));
		return NT_STATUS_ACCESS_DENIED;
	}
	/*
	 * - The Open.GrantedAccess of the source file does not include
	 *   FILE_READ_DATA access.
	 */
	if (!CHECK_READ_SMB2(src_fsp)) {
		DBG_INFO("src handle not readable (%s).\n",
			 smb_fname_str_dbg(src_fsp->fsp_name));
		return NT_STATUS_ACCESS_DENIED;
	}

	return NT_STATUS_OK;
}
