/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
 * Copyright 2019 RackTop Systems.
 */

/*
 * Dispatch function for SMB2_CREATE
 * [MS-SMB2] 2.2.13
 */

#include <smbsrv/smb2_kproto.h>
#include <smbsrv/smb_fsops.h>

#define	DH_PERSISTENT	SMB2_DHANDLE_FLAG_PERSISTENT

/*
 * Compile-time check that the SMB2_LEASE_... definitions
 * match the (internal) equivalents from ntifs.h
 */
#if SMB2_LEASE_NONE != OPLOCK_LEVEL_NONE
#error "SMB2_LEASE_NONE"
#endif
#if SMB2_LEASE_READ_CACHING != OPLOCK_LEVEL_CACHE_READ
#error "SMB2_LEASE_READ_CACHING"
#endif
#if SMB2_LEASE_HANDLE_CACHING != OPLOCK_LEVEL_CACHE_HANDLE
#error "SMB2_LEASE_HANDLE_CACHING"
#endif
#if SMB2_LEASE_WRITE_CACHING != OPLOCK_LEVEL_CACHE_WRITE
#error "SMB2_LEASE_WRITE_CACHING"
#endif

/*
 * Some flags used locally to keep track of which Create Context
 * names have been provided and/or requested.
 */
#define	CCTX_EA_BUFFER			1
#define	CCTX_SD_BUFFER			2
#define	CCTX_DH_REQUEST			4
#define	CCTX_DH_RECONNECT		8
#define	CCTX_ALLOCATION_SIZE		0x10
#define	CCTX_QUERY_MAX_ACCESS		0x20
#define	CCTX_TIMEWARP_TOKEN		0x40
#define	CCTX_QUERY_ON_DISK_ID		0x80
#define	CCTX_REQUEST_LEASE		0x100
#define	CCTX_AAPL_EXT			0x200
#define	CCTX_DH_REQUEST_V2		0x400
#define	CCTX_DH_RECONNECT_V2		0x800

typedef struct smb2_create_ctx_elem {
	uint32_t cce_len;
	mbuf_chain_t cce_mbc;
} smb2_create_ctx_elem_t;

typedef struct smb2_create_ctx {
	mbuf_chain_t cc_in_mbc;
	uint_t	cc_in_flags;	/* CCTX_... */
	uint_t	cc_out_flags;	/* CCTX_... */
	/* Elements we may see in the request. */
	smb2_create_ctx_elem_t cc_in_ext_attr;
	smb2_create_ctx_elem_t cc_in_sec_desc;
	smb2_create_ctx_elem_t cc_in_dh_request;
	smb2_create_ctx_elem_t cc_in_dh_reconnect;
	smb2_create_ctx_elem_t cc_in_alloc_size;
	smb2_create_ctx_elem_t cc_in_time_warp;
	smb2_create_ctx_elem_t cc_in_req_lease;
	smb2_create_ctx_elem_t cc_in_aapl;
	smb2_create_ctx_elem_t cc_in_dh_request_v2;
	smb2_create_ctx_elem_t cc_in_dh_reconnect_v2;
	smb2_create_ctx_elem_t cc_in_max_access;
	/* Elements we my place in the response */
	smb2_create_ctx_elem_t cc_out_max_access;
	smb2_create_ctx_elem_t cc_out_file_id;
	smb2_create_ctx_elem_t cc_out_aapl;
	smb2_create_ctx_elem_t cc_out_req_lease;
	smb2_create_ctx_elem_t cc_out_dh_request;
	smb2_create_ctx_elem_t cc_out_dh_request_v2;
} smb2_create_ctx_t;

static uint32_t smb2_decode_create_ctx(
	smb_request_t *, smb2_create_ctx_t *);
static uint32_t smb2_encode_create_ctx(
	smb_request_t *, smb2_create_ctx_t *);
static int smb2_encode_create_ctx_elem(
	mbuf_chain_t *, smb2_create_ctx_elem_t *, uint32_t);
static void smb2_free_create_ctx(smb2_create_ctx_t *);

int smb2_enable_dh = 1;

smb_sdrc_t
smb2_create(smb_request_t *sr)
{
	smb_attr_t *attr;
	smb2_create_ctx_elem_t *cce;
	smb2_create_ctx_t cctx;
	smb_arg_open_t *op = &sr->arg.open;
	smb_ofile_t *of = NULL;
	uint16_t StructSize;
	uint8_t SecurityFlags;
	uint32_t ImpersonationLevel;
	uint64_t SmbCreateFlags;
	uint64_t Reserved4;
	uint16_t NameOffset;
	uint16_t NameLength;
	uint32_t CreateCtxOffset;
	uint32_t CreateCtxLength;
	smb2fid_t smb2fid = { 0, 0 };
	uint32_t status;
	int dh_flags;
	int skip;
	int rc = 0;

	bzero(&cctx, sizeof (cctx));
	op->create_ctx = &cctx;	/* for debugging */

	/*
	 * Paranoia.  This will set sr->fid_ofile, so
	 * if we already have one, release it now.
	 */
	if (sr->fid_ofile != NULL) {
		smb_ofile_release(sr->fid_ofile);
		sr->fid_ofile = NULL;
	}

	/*
	 * Decode the SMB2 Create request
	 *
	 * Most decode errors return SDRC_ERROR, but
	 * for some we give a more specific error.
	 *
	 * In the "decode section" (starts here) any
	 * errors should either return SDRC_ERROR, or
	 * if any cleanup is needed, goto errout.
	 */
	rc = smb_mbc_decodef(
	    &sr->smb_data, "wbblqqlllllwwll",
	    &StructSize,		/* w */
	    &SecurityFlags,		/* b */
	    &op->op_oplock_level,	/* b */
	    &ImpersonationLevel,	/* l */
	    &SmbCreateFlags,		/* q */
	    &Reserved4,			/* q */
	    &op->desired_access,	/* l */
	    &op->dattr,			/* l */
	    &op->share_access,		/* l */
	    &op->create_disposition,	/* l */
	    &op->create_options,	/* l */
	    &NameOffset,		/* w */
	    &NameLength,		/* w */
	    &CreateCtxOffset,		/* l */
	    &CreateCtxLength);		/* l */
	if (rc != 0 || StructSize != 57)
		return (SDRC_ERROR);

	/*
	 * We're normally positioned at the path name now,
	 * but there could be some padding before it.
	 */
	skip = (NameOffset + sr->smb2_cmd_hdr) -
	    sr->smb_data.chain_offset;
	if (skip < 0)
		return (SDRC_ERROR);
	if (skip > 0)
		(void) smb_mbc_decodef(&sr->smb_data, "#.", skip);

	/*
	 * Get the path name
	 *
	 * Name too long is not technically a decode error,
	 * but it's very rare, so we'll just skip the
	 * dtrace probes for this error case.
	 */
	if (NameLength >= SMB_MAXPATHLEN) {
		status = NT_STATUS_OBJECT_PATH_INVALID;
		goto errout;
	}
	if (NameLength == 0) {
		op->fqi.fq_path.pn_path = "";
	} else {
		rc = smb_mbc_decodef(&sr->smb_data, "%#U", sr,
		    NameLength, &op->fqi.fq_path.pn_path);
		if (rc) {
			status = NT_STATUS_OBJECT_PATH_INVALID;
			goto errout;
		}
		if (op->fqi.fq_path.pn_path[0] == '\\') {
			status = NT_STATUS_INVALID_PARAMETER;
			goto errout;
		}
	}
	op->fqi.fq_dnode = sr->tid_tree->t_snode;

	/*
	 * If there is a "Create Context" payload, decode it.
	 * This may carry things like a security descriptor,
	 * extended attributes, etc. to be used in create.
	 *
	 * The create ctx buffer must start after the headers
	 * and file name, and must be 8-byte aligned.
	 */
	if (CreateCtxLength != 0) {
		if ((CreateCtxOffset & 7) != 0 ||
		    (CreateCtxOffset + sr->smb2_cmd_hdr) <
		    sr->smb_data.chain_offset) {
			status = NT_STATUS_INVALID_PARAMETER;
			goto errout;
		}

		rc = MBC_SHADOW_CHAIN(&cctx.cc_in_mbc, &sr->smb_data,
		    sr->smb2_cmd_hdr + CreateCtxOffset, CreateCtxLength);
		if (rc) {
			status = NT_STATUS_INVALID_PARAMETER;
			goto errout;
		}
		status = smb2_decode_create_ctx(sr, &cctx);
		if (status)
			goto errout;
	}

	/*
	 * Everything is decoded into some internal form, so
	 * in this probe one can look at sr->arg.open etc.
	 *
	 * This marks the end of the "decode" section and the
	 * beginning of the "body" section.  Any errors in
	 * this section should use: goto cmd_done (which is
	 * just before the dtrace "done" probe).
	 */
	DTRACE_SMB2_START(op__Create, smb_request_t *, sr); /* arg.open */

	/*
	 * Process the incoming create contexts (already decoded),
	 * that need action before the open, starting with the
	 * Durable Handle ones, which may override others.
	 */

	/*
	 * Only disk trees get durable handles.
	 */
	if (smb2_enable_dh == 0 ||
	    (sr->tid_tree->t_res_type & STYPE_MASK) != STYPE_DISKTREE) {
		cctx.cc_in_flags &=
		    ~(CCTX_DH_REQUEST | CCTX_DH_REQUEST_V2 |
		    CCTX_DH_RECONNECT | CCTX_DH_RECONNECT_V2);
	}

	/*
	 * DH v2 is only valid in SMB3.0 and later.
	 * If seen in earlier dialects, ignore.
	 */
	if (sr->session->dialect < SMB_VERS_3_0) {
		cctx.cc_in_flags &=
		    ~(CCTX_DH_REQUEST_V2|CCTX_DH_RECONNECT_V2);
	}

	/*
	 * It is an error to specify more than one Durable Handle
	 * operation in a single create, except when only the v1
	 * REQUEST and RECONNECT operations are specified. In that
	 * case, the v1 REQUEST is ignored.
	 */
	dh_flags = cctx.cc_in_flags &
	    (CCTX_DH_REQUEST | CCTX_DH_REQUEST_V2 |
	    CCTX_DH_RECONNECT | CCTX_DH_RECONNECT_V2);
	if ((dh_flags & (dh_flags - 1)) != 0 &&
	    dh_flags != (CCTX_DH_REQUEST|CCTX_DH_RECONNECT)) {
		status = NT_STATUS_INVALID_PARAMETER;
		goto cmd_done;
	}

	/*
	 * Reconnect is special in MANY ways, including the
	 * somewhat surprising (specified) behavior that
	 * most other creat parameters are ignored, and
	 * many create context types are ignored too.
	 */
	op->dh_vers = SMB2_NOT_DURABLE;
	if ((cctx.cc_in_flags &
	    (CCTX_DH_RECONNECT|CCTX_DH_RECONNECT_V2)) != 0) {

		if ((cctx.cc_in_flags & CCTX_DH_RECONNECT_V2) != 0)
			op->dh_vers = SMB2_DURABLE_V2;
		else
			op->dh_vers = SMB2_DURABLE_V1;

		/* Ignore these create contexts. */
		cctx.cc_in_flags &=
		    ~(CCTX_DH_REQUEST |
		    CCTX_DH_REQUEST_V2 |
		    CCTX_EA_BUFFER |
		    CCTX_SD_BUFFER |
		    CCTX_ALLOCATION_SIZE |
		    CCTX_TIMEWARP_TOKEN |
		    CCTX_QUERY_ON_DISK_ID);

		/*
		 * Reconnect check needs to know if a lease was requested.
		 * The requested oplock level is ignored in reconnect, so
		 * using op_oplock_level to convey this info.
		 */
		if (cctx.cc_in_flags & CCTX_REQUEST_LEASE)
			op->op_oplock_level = SMB2_OPLOCK_LEVEL_LEASE;
		else
			op->op_oplock_level = 0;

		status = smb2_dh_reconnect(sr);
		if (status != NT_STATUS_SUCCESS)
			goto cmd_done;

		/*
		 * Skip most open execution during reconnect,
		 * but need (reclaimed) oplock state in *op.
		 */
		of = sr->fid_ofile;
		smb2_oplock_reconnect(sr);
		goto reconnect_done;
	}

	/*
	 * Real create (of a new handle, not reconnect)
	 */

	/*
	 * Validate the requested oplock level.
	 * Conversion to internal form is in smb2_oplock_acquire()
	 */
	switch (op->op_oplock_level) {
	case SMB2_OPLOCK_LEVEL_NONE:		/* OPLOCK_LEVEL_NONE */
	case SMB2_OPLOCK_LEVEL_II:		/* OPLOCK_LEVEL_TWO */
	case SMB2_OPLOCK_LEVEL_EXCLUSIVE:	/* OPLOCK_LEVEL_ONE */
	case SMB2_OPLOCK_LEVEL_BATCH:		/* OPLOCK_LEVEL_BATCH */
		/*
		 * Ignore lease create context (if any)
		 */
		cctx.cc_in_flags &= ~CCTX_REQUEST_LEASE;
		break;

	case SMB2_OPLOCK_LEVEL_LEASE:		/* OPLOCK_LEVEL_GRANULAR */
		/*
		 * Require a lease create context.
		 */
		if ((cctx.cc_in_flags & CCTX_REQUEST_LEASE) == 0) {
			cmn_err(CE_NOTE, "smb2:create, oplock=ff and no lease");
			status = NT_STATUS_INVALID_PARAMETER;
			goto cmd_done;
		}

		/*
		 * Validate lease request state
		 * Only a few valid combinations.
		 */
		switch (op->lease_state) {
		case SMB2_LEASE_NONE:
		case SMB2_LEASE_READ_CACHING:
		case SMB2_LEASE_READ_CACHING | SMB2_LEASE_HANDLE_CACHING:
		case SMB2_LEASE_READ_CACHING | SMB2_LEASE_WRITE_CACHING:
		case SMB2_LEASE_READ_CACHING | SMB2_LEASE_WRITE_CACHING |
		    SMB2_LEASE_HANDLE_CACHING:
			break;

		default:
			/*
			 * Invalid lease state flags
			 * Just force to "none".
			 */
			op->lease_state = SMB2_LEASE_NONE;
			break;
		}
		break;

	default:
		/* Unknown SMB2 oplock level. */
		status = NT_STATUS_INVALID_PARAMETER;
		goto cmd_done;
	}

	/*
	 * Only disk trees get oplocks or leases.
	 */
	if ((sr->tid_tree->t_res_type & STYPE_MASK) != STYPE_DISKTREE) {
		op->op_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
		cctx.cc_in_flags &= ~CCTX_REQUEST_LEASE;
	}

	if ((sr->tid_tree->t_flags & SMB_TREE_CA) == 0)
		op->dh_v2_flags &= ~DH_PERSISTENT;

	if ((cctx.cc_in_flags &
	    (CCTX_DH_REQUEST|CCTX_DH_REQUEST_V2)) != 0) {
		if ((cctx.cc_in_flags & CCTX_DH_REQUEST_V2) != 0)
			op->dh_vers = SMB2_DURABLE_V2;
		else
			op->dh_vers = SMB2_DURABLE_V1;
	}

	if (cctx.cc_in_flags & CCTX_EA_BUFFER) {
		status = NT_STATUS_EAS_NOT_SUPPORTED;
		goto cmd_done;
	}

	/*
	 * ImpersonationLevel (spec. says validate + ignore)
	 */
	switch (ImpersonationLevel) {
	case SMB2_IMPERSONATION_ANONYMOUS:
	case SMB2_IMPERSONATION_IDENTIFICATION:
	case SMB2_IMPERSONATION_IMPERSONATION:
	case SMB2_IMPERSONATION_DELEGATE:
		break;
	default:
		status = NT_STATUS_BAD_IMPERSONATION_LEVEL;
		goto cmd_done;
	}

	/*
	 * SmbCreateFlags (spec. says ignore)
	 */

	if ((op->create_options & FILE_DELETE_ON_CLOSE) &&
	    !(op->desired_access & DELETE)) {
		status = NT_STATUS_INVALID_PARAMETER;
		goto cmd_done;
	}

	if (op->dattr & FILE_FLAG_WRITE_THROUGH)
		op->create_options |= FILE_WRITE_THROUGH;
	if (op->dattr & FILE_FLAG_DELETE_ON_CLOSE)
		op->create_options |= FILE_DELETE_ON_CLOSE;
	if (op->dattr & FILE_FLAG_BACKUP_SEMANTICS)
		op->create_options |= FILE_OPEN_FOR_BACKUP_INTENT;
	if (op->create_options & FILE_OPEN_FOR_BACKUP_INTENT)
		sr->user_cr = smb_user_getprivcred(sr->uid_user);
	if (op->create_disposition > FILE_MAXIMUM_DISPOSITION) {
		status = NT_STATUS_INVALID_PARAMETER;
		goto cmd_done;
	}

	/*
	 * The real open call.   Note: this gets attributes into
	 * op->fqi.fq_fattr (SMB_AT_ALL).  We need those below.
	 * When of != NULL, goto errout closes it.
	 */
	status = smb_common_open(sr);
	if (status != NT_STATUS_SUCCESS)
		goto cmd_done;
	of = sr->fid_ofile;

	/*
	 * Set the "persistent" part of the file ID
	 * (only for DISK shares).  Need this even for
	 * non-durable handles in case we get the ioctl
	 * to set "resiliency" on this handle.
	 */
	if (of->f_ftype == SMB_FTYPE_DISK) {
		if ((op->dh_v2_flags & DH_PERSISTENT) != 0)
			smb_ofile_set_persistid_ph(of);
		else
			smb_ofile_set_persistid_dh(of);
	}

	/*
	 * [MS-SMB2] 3.3.5.9.8
	 * Handling the SMB2_CREATE_REQUEST_LEASE Create Context
	 */
	if ((cctx.cc_in_flags & CCTX_REQUEST_LEASE) != 0) {
		status = smb2_lease_create(sr, sr->session->clnt_uuid);
		if (status != NT_STATUS_SUCCESS) {
			if (op->action_taken == SMB_OACT_CREATED) {
				smb_ofile_set_delete_on_close(sr, of);
			}
			goto cmd_done;
		}
	}
	if (op->op_oplock_level == SMB2_OPLOCK_LEVEL_LEASE) {
		smb2_lease_acquire(sr);
	} else if (op->op_oplock_level != SMB2_OPLOCK_LEVEL_NONE) {
		smb2_oplock_acquire(sr);
	}

	/*
	 * Make this a durable open, but only if:
	 * (durable handle requested and...)
	 *
	 * 1. op_oplock_level == SMB2_OPLOCK_LEVEL_BATCH
	 * 2. A lease is requested with handle caching
	 *    - for v1, the lease must not be on a directory
	 * 3. For v2, flags has "persistent" (tree is CA)
	 *    (when tree not CA, turned off persist above)
	 *
	 * Otherwise, DH requests are ignored, so we set
	 * dh_vers = not durable
	 */
	if ((cctx.cc_in_flags &
	    (CCTX_DH_REQUEST|CCTX_DH_REQUEST_V2)) != 0 &&
	    smb_node_is_file(of->f_node) &&
	    ((op->dh_v2_flags & DH_PERSISTENT) != 0 ||
	    (op->op_oplock_level == SMB2_OPLOCK_LEVEL_BATCH) ||
	    (op->op_oplock_level == SMB2_OPLOCK_LEVEL_LEASE &&
	    (op->lease_state & OPLOCK_LEVEL_CACHE_HANDLE) != 0))) {
		/*
		 * OK, make this handle "durable"
		 */
		if (op->dh_vers == SMB2_DURABLE_V2) {
			(void) memcpy(of->dh_create_guid,
			    op->create_guid, UUID_LEN);

			if ((op->dh_v2_flags & DH_PERSISTENT) != 0) {
				if (smb2_dh_make_persistent(sr, of) == 0) {
					of->dh_persist = B_TRUE;
				} else {
					op->dh_v2_flags = 0;
				}
			}
		}
		if (op->dh_vers != SMB2_NOT_DURABLE) {
			uint32_t msto;

			of->dh_vers = op->dh_vers;
			of->dh_expire_time = 0;

			/*
			 * Client may provide timeout=0 to request
			 * the default timeout (in mSec.)
			 */
			msto = op->dh_timeout;
			if (msto == 0) {
				msto = (of->dh_persist) ?
				    smb2_persist_timeout :
				    smb2_dh_def_timeout;
			}
			if (msto > smb2_dh_max_timeout)
				msto = smb2_dh_max_timeout;
			op->dh_timeout = msto;
			of->dh_timeout_offset = MSEC2NSEC(msto);
		}
	} else {
		op->dh_vers = SMB2_NOT_DURABLE;
		op->dh_v2_flags = 0;
	}

	/*
	 * NB: after the above smb_common_open() success,
	 * we have a handle allocated (sr->fid_ofile).
	 * If we don't return success, we must close it.
	 *
	 * Using sr->smb_fid as the file handle for now,
	 * though it could later be something larger,
	 * (16 bytes) similar to an NFSv4 open handle.
	 */
reconnect_done:
	smb2fid.persistent = of->f_persistid;
	smb2fid.temporal = sr->smb_fid;

	switch (sr->tid_tree->t_res_type & STYPE_MASK) {
	case STYPE_DISKTREE:
	case STYPE_PRINTQ:
		if (op->create_options & FILE_DELETE_ON_CLOSE)
			smb_ofile_set_delete_on_close(sr, of);
		break;
	}

	/*
	 * Process any outgoing create contexts that need work
	 * after the open succeeds.  Encode happens later.
	 */
	if (cctx.cc_in_flags & CCTX_QUERY_MAX_ACCESS) {
		op->maximum_access = 0;
		if (of->f_node != NULL) {
			smb_fsop_eaccess(sr, of->f_cr, of->f_node,
			    &op->maximum_access);
		}
		op->maximum_access |= of->f_granted_access;
		cctx.cc_out_flags |= CCTX_QUERY_MAX_ACCESS;
	}

	if ((cctx.cc_in_flags & CCTX_QUERY_ON_DISK_ID) != 0 &&
	    of->f_node != NULL) {
		op->op_fsid = SMB_NODE_FSID(of->f_node);
		cctx.cc_out_flags |= CCTX_QUERY_ON_DISK_ID;
	}

	if ((cctx.cc_in_flags & CCTX_AAPL_EXT) != 0) {
		cce = &cctx.cc_out_aapl;
		/*
		 * smb2_aapl_crctx has a variable response depending on
		 * what the incoming context looks like, so it does all
		 * the work of building cc_out_aapl, including setting
		 * cce_len, cce_mbc.max_bytes, and smb_mbc_encode.
		 * If we see errors getting this, simply omit it from
		 * the collection of returned create contexts.
		 */
		status = smb2_aapl_crctx(sr,
		    &cctx.cc_in_aapl.cce_mbc, &cce->cce_mbc);
		if (status == 0) {
			cce->cce_len = cce->cce_mbc.chain_offset;
			cctx.cc_out_flags |= CCTX_AAPL_EXT;
		}
		status = 0;
	}

	/*
	 * If a lease was requested, and we got one...
	 */
	if ((cctx.cc_in_flags & CCTX_REQUEST_LEASE) != 0 &&
	    op->op_oplock_level == SMB2_OPLOCK_LEVEL_LEASE)
		cctx.cc_out_flags |= CCTX_REQUEST_LEASE;

	/*
	 * If a durable handle was requested and we got one...
	 */
	if ((cctx.cc_in_flags & CCTX_DH_REQUEST) != 0 &&
	    of->dh_vers == SMB2_DURABLE_V1) {
		cctx.cc_out_flags |= CCTX_DH_REQUEST;
	}
	if ((cctx.cc_in_flags & CCTX_DH_REQUEST_V2) != 0 &&
	    of->dh_vers == SMB2_DURABLE_V2) {
		cctx.cc_out_flags |= CCTX_DH_REQUEST_V2;
	}

	/*
	 * This marks the end of the "body" section and the
	 * beginning of the "encode" section.  Any errors
	 * encoding the response should use: goto errout
	 */
cmd_done:
	/* Want status visible in the done probe. */
	sr->smb2_status = status;
	DTRACE_SMB2_DONE(op__Create, smb_request_t *, sr);
	if (status != NT_STATUS_SUCCESS)
		goto errout;

	/*
	 * Encode all the create contexts to return.
	 */
	if (cctx.cc_out_flags) {
		sr->raw_data.max_bytes = smb2_max_trans;
		status = smb2_encode_create_ctx(sr, &cctx);
		if (status)
			goto errout;
	}

	/*
	 * Encode the SMB2 Create reply
	 */
	attr = &op->fqi.fq_fattr;
	rc = smb_mbc_encodef(
	    &sr->reply,
	    "wb.lTTTTqqllqqll",
	    89,	/* StructSize */	/* w */
	    op->op_oplock_level,	/* b */
	    op->action_taken,		/* l */
	    &attr->sa_crtime,		/* T */
	    &attr->sa_vattr.va_atime,	/* T */
	    &attr->sa_vattr.va_mtime,	/* T */
	    &attr->sa_vattr.va_ctime,	/* T */
	    attr->sa_allocsz,		/* q */
	    attr->sa_vattr.va_size,	/* q */
	    attr->sa_dosattr,		/* l */
	    0, /* reserved2 */		/* l */
	    smb2fid.persistent,		/* q */
	    smb2fid.temporal,		/* q */
	    0,  /* CreateCtxOffset	   l */
	    0); /* CreateCtxLength	   l */
	if (rc != 0) {
		status = NT_STATUS_UNSUCCESSFUL;
		goto errout;
	}

	CreateCtxOffset = sr->reply.chain_offset - sr->smb2_reply_hdr;
	CreateCtxLength = MBC_LENGTH(&sr->raw_data);
	if (CreateCtxLength != 0) {
		/*
		 * Overwrite CreateCtxOffset, CreateCtxLength, pad
		 */
		sr->reply.chain_offset -= 8;
		rc = smb_mbc_encodef(
		    &sr->reply,
		    "ll#C",
		    CreateCtxOffset,	/* l */
		    CreateCtxLength,	/* l */
		    CreateCtxLength,	/* # */
		    &sr->raw_data);	/* C */
		if (rc != 0) {
			status = NT_STATUS_UNSUCCESSFUL;
			goto errout;
		}
	} else {
		(void) smb_mbc_encodef(&sr->reply, ".");
	}

	if (status != 0) {
	errout:
		if (of != NULL)
			smb_ofile_close(of, 0);
		smb2sr_put_error(sr, status);
	}
	if (op->sd != NULL) {
		smb_sd_term(op->sd);
		kmem_free(op->sd, sizeof (*op->sd));
	}
	if (cctx.cc_out_flags)
		smb2_free_create_ctx(&cctx);

	return (SDRC_SUCCESS);
}

/*
 * Decode an SMB2 Create Context buffer into our internal form.
 * Avoid policy decisions about what's supported here, just decode.
 */
static uint32_t
smb2_decode_create_ctx(smb_request_t *sr, smb2_create_ctx_t *cc)
{
	smb_arg_open_t *op = &sr->arg.open;
	smb2_create_ctx_elem_t *cce;
	mbuf_chain_t *in_mbc = &cc->cc_in_mbc;
	mbuf_chain_t name_mbc;
	union {
		uint32_t i;
		char ch[4];
	} cc_name;
	uint32_t status;
	int32_t next_off;
	uint32_t data_len;
	uint16_t data_off;
	uint16_t name_off;
	uint16_t name_len;
	int top_offset;
	int rc;

	/*
	 * Any break from the loop below before we've decoded
	 * the entire create context means it was malformatted,
	 * so we should return INVALID_PARAMETER.
	 */
	status = NT_STATUS_INVALID_PARAMETER;
	for (;;) {
		cce = NULL;
		top_offset = in_mbc->chain_offset;
		rc = smb_mbc_decodef(
		    in_mbc,
		    "lww..wl",
		    &next_off,	/* l */
		    &name_off,	/* w */
		    &name_len,	/* w */
		    /* reserved	  .. */
		    &data_off,	/* w */
		    &data_len); /* l */
		if (rc)
			break;

		/*
		 * The Create Context "name", per [MS-SMB] 2.2.13.2
		 * They're defined as network-order integers for our
		 * switch below.  We don't have routines to decode
		 * native order, so read as char[4] then ntohl.
		 * NB: in SMB3, some of these are 8 bytes.
		 */
		if ((top_offset + name_off) < in_mbc->chain_offset)
			break;
		rc = MBC_SHADOW_CHAIN(&name_mbc, in_mbc,
		    top_offset + name_off, name_len);
		if (rc)
			break;
		rc = smb_mbc_decodef(&name_mbc, "4c", &cc_name);
		if (rc)
			break;
		cc_name.i = ntohl(cc_name.i);

		switch (cc_name.i) {
		case SMB2_CREATE_EA_BUFFER:		/* ("ExtA") */
			cc->cc_in_flags |= CCTX_EA_BUFFER;
			cce = &cc->cc_in_ext_attr;
			break;
		case SMB2_CREATE_SD_BUFFER:		/* ("SecD") */
			cc->cc_in_flags |= CCTX_SD_BUFFER;
			cce = &cc->cc_in_sec_desc;
			break;
		case SMB2_CREATE_DURABLE_HANDLE_REQUEST: /* ("DHnQ") */
			cc->cc_in_flags |= CCTX_DH_REQUEST;
			cce = &cc->cc_in_dh_request;
			break;
		case SMB2_CREATE_DURABLE_HANDLE_RECONNECT: /* ("DHnC") */
			cc->cc_in_flags |= CCTX_DH_RECONNECT;
			cce = &cc->cc_in_dh_reconnect;
			break;
		case SMB2_CREATE_ALLOCATION_SIZE:	/* ("AISi") */
			cc->cc_in_flags |= CCTX_ALLOCATION_SIZE;
			cce = &cc->cc_in_alloc_size;
			break;
		case SMB2_CREATE_QUERY_MAXIMAL_ACCESS_REQ: /* ("MxAc") */
			cc->cc_in_flags |= CCTX_QUERY_MAX_ACCESS;
			/* Optional input data for this CC. See below. */
			cce = &cc->cc_in_max_access;
			break;
		case SMB2_CREATE_TIMEWARP_TOKEN:	/* ("TWrp") */
			cc->cc_in_flags |= CCTX_TIMEWARP_TOKEN;
			cce = &cc->cc_in_time_warp;
			break;
		case SMB2_CREATE_QUERY_ON_DISK_ID:	/* ("QFid") */
			cc->cc_in_flags |= CCTX_QUERY_ON_DISK_ID;
			/* no input data for this */
			break;
		case SMB2_CREATE_REQUEST_LEASE:		/* ("RqLs") */
			cc->cc_in_flags |= CCTX_REQUEST_LEASE;
			cce = &cc->cc_in_req_lease;
			break;
		case SMB2_CREATE_CTX_AAPL:		/* ("AAPL") */
			cc->cc_in_flags |= CCTX_AAPL_EXT;
			cce = &cc->cc_in_aapl;
			break;
		case SMB2_CREATE_DURABLE_HANDLE_REQUEST_V2: /* ("DH2Q") */
			cc->cc_in_flags |= CCTX_DH_REQUEST_V2;
			cce = &cc->cc_in_dh_request_v2;
			break;
		case SMB2_CREATE_DURABLE_HANDLE_RECONNECT_V2: /* ("DH2C") */
			cc->cc_in_flags |= CCTX_DH_RECONNECT_V2;
			cce = &cc->cc_in_dh_reconnect_v2;
			break;
		case 0x9ccbcf9e: /* SVHDX_OPEN_DEVICE_CONTEXT */
			/* 9ccbcf9e 04c1e643 980e158d a1f6ec83 */
			/* silently ignore */
			break;
		default:
			/*
			 * Unknown create context values are normal, and
			 * should be ignored.  However, in debug mode,
			 * let's log them so we know which ones we're
			 * not handling (and may want to add).
			 */
#ifdef	DEBUG
			cmn_err(CE_NOTE, "unknown create context ID 0x%x",
			    cc_name.i);
#endif
			cce = NULL;
			break;
		}

		if (cce == NULL || data_len == 0)
			goto next_cc;

		if ((data_off & 7) != 0)
			break;
		if ((top_offset + data_off) < in_mbc->chain_offset)
			break;
		rc = MBC_SHADOW_CHAIN(&cce->cce_mbc, in_mbc,
		    top_offset + data_off, data_len);
		if (rc)
			break;
		cce->cce_len = data_len;

		/*
		 * Additonal decoding for some create contexts.
		 */
		switch (cc_name.i) {
			uint64_t nttime;

		case SMB2_CREATE_SD_BUFFER:		/* ("SecD") */
			op->sd = kmem_alloc(sizeof (smb_sd_t), KM_SLEEP);
			if (smb_decode_sd(&cce->cce_mbc, op->sd) != 0)
				goto errout;
			break;

		case SMB2_CREATE_ALLOCATION_SIZE:	/* ("AISi") */
			rc = smb_mbc_decodef(&cce->cce_mbc, "q", &op->dsize);
			if (rc != 0)
				goto errout;
			break;

		case SMB2_CREATE_QUERY_MAXIMAL_ACCESS_REQ: /* ("MxAc") */
			/*
			 * The SMB spec says this can be either 0 bytes
			 * (handled above) or an 8 byte timestamp value
			 * but does not say what its purpose is.
			 *
			 * Note: The WPTS expects us to validate that it
			 * is at least 8 bytes so we read it and discard
			 * it.  If it was too short the decode will fail.
			 */
			rc = smb_mbc_decodef(&cce->cce_mbc, "q", &nttime);
			if (rc != 0)
				goto errout;
			break;

		case SMB2_CREATE_TIMEWARP_TOKEN:	/* ("TWrp") */
			/*
			 * Support for opening "Previous Versions".
			 * [MS-SMB2] 2.2.13.2.7  Data is an NT time.
			 */
			rc = smb_mbc_decodef(&cce->cce_mbc,
			    "q", &nttime);
			if (rc != 0)
				goto errout;
			smb_time_nt_to_unix(nttime, &op->timewarp);
			op->create_timewarp = B_TRUE;
			break;

		/*
		 * Note: This handles both V1 and V2 leases,
		 * which differ only by their length.
		 */
		case SMB2_CREATE_REQUEST_LEASE:		/* ("RqLs") */
			if (data_len == 52) {
				op->lease_version = 2;
			} else if (data_len == 32) {
				op->lease_version = 1;
			} else {
				cmn_err(CE_NOTE, "Cctx RqLs bad len=0x%x",
				    data_len);
			}
			rc = smb_mbc_decodef(&cce->cce_mbc, "#cllq",
			    UUID_LEN,			/* # */
			    op->lease_key,		/* c */
			    &op->lease_state,		/* l */
			    &op->lease_flags,		/* l */
			    &nttime);	/* (ignored)	   q */
			if (rc != 0)
				goto errout;
			if (op->lease_version == 2) {
				rc = smb_mbc_decodef(&cce->cce_mbc,
				    "#cw..",
				    UUID_LEN,
				    op->parent_lease_key,
				    &op->lease_epoch);
				if (rc != 0)
					goto errout;
			} else {
				bzero(op->parent_lease_key, UUID_LEN);
			}
			break;

		case SMB2_CREATE_DURABLE_HANDLE_RECONNECT_V2: /* ("DH2C") */
			rc = smb_mbc_decodef(&cce->cce_mbc, "qq#cl",
			    &op->dh_fileid.persistent,	/* q */
			    &op->dh_fileid.temporal,	/* q */
			    UUID_LEN,			/* # */
			    op->create_guid,		/* c */
			    &op->dh_v2_flags);		/* l */
			if (rc != 0)
				goto errout;
			break;

		case SMB2_CREATE_DURABLE_HANDLE_RECONNECT: /* ("DHnC") */
			rc = smb_mbc_decodef(&cce->cce_mbc, "qq",
			    &op->dh_fileid.persistent, /* q */
			    &op->dh_fileid.temporal); /* q */
			if (rc != 0)
				goto errout;
			bzero(op->create_guid, UUID_LEN);
			op->dh_v2_flags = 0;
			break;

		case SMB2_CREATE_DURABLE_HANDLE_REQUEST_V2: /* ("DH2Q") */
			rc = smb_mbc_decodef(&cce->cce_mbc,
			    "ll8.#c",
			    &op->dh_timeout,	/* l */
			    &op->dh_v2_flags,	/* l */
			    /* reserved */	/* 8. */
			    UUID_LEN, /* # */
			    op->create_guid); /* c */
			if (rc != 0)
				goto errout;
			break;

		case SMB2_CREATE_DURABLE_HANDLE_REQUEST: /* ("DHnQ") */
			rc = smb_mbc_decodef(&cce->cce_mbc,
			    "16."); /* reserved */
			if (rc != 0)
				goto errout;
			op->dh_timeout = 0;	/* default */
			op->dh_v2_flags = 0;
			break;
		}

	next_cc:
		if (next_off == 0) {
			/* Normal loop termination */
			status = 0;
			break;
		}

		if ((next_off & 7) != 0)
			break;
		if ((top_offset + next_off) < in_mbc->chain_offset)
			break;
		if ((top_offset + next_off) > in_mbc->max_bytes)
			break;
		in_mbc->chain_offset = top_offset + next_off;
	}

errout:
	return (status);
}

/*
 * Encode an SMB2 Create Context buffer from our internal form.
 *
 * Build the Create Context to return; first the
 * per-element parts, then the aggregated buffer.
 *
 * No response for these:
 *	CCTX_EA_BUFFER
 *	CCTX_SD_BUFFER
 *	CCTX_ALLOCATION_SIZE
 *	CCTX_TIMEWARP_TOKEN
 *
 * Remember to add code sections to smb2_free_create_ctx()
 * for each section here that encodes a context element.
 */
static uint32_t
smb2_encode_create_ctx(smb_request_t *sr, smb2_create_ctx_t *cc)
{
	smb_arg_open_t *op = &sr->arg.open;
	smb2_create_ctx_elem_t *cce;
	mbuf_chain_t *mbc = &sr->raw_data;
	int last_top = -1;
	int rc;

	if (cc->cc_out_flags & CCTX_QUERY_MAX_ACCESS) {
		cce = &cc->cc_out_max_access;

		cce->cce_mbc.max_bytes = cce->cce_len = 8;
		(void) smb_mbc_encodef(&cce->cce_mbc,
		    "ll", 0, op->maximum_access);

		last_top = mbc->chain_offset;
		rc = smb2_encode_create_ctx_elem(mbc, cce,
		    SMB2_CREATE_QUERY_MAXIMAL_ACCESS_REQ);
		if (rc)
			return (NT_STATUS_INTERNAL_ERROR);
		(void) smb_mbc_poke(mbc, last_top, "l",
		    mbc->chain_offset - last_top);
	}

	if (cc->cc_out_flags & CCTX_QUERY_ON_DISK_ID) {
		cce = &cc->cc_out_file_id;

		cce->cce_mbc.max_bytes = cce->cce_len = 32;
		(void) smb_mbc_encodef(
		    &cce->cce_mbc, "qll.15.",
		    op->fileid,			/* q */
		    op->op_fsid.val[0],		/* l */
		    op->op_fsid.val[1]);	/* l */
		    /* reserved (16 bytes)	.15. */

		last_top = mbc->chain_offset;
		rc = smb2_encode_create_ctx_elem(mbc, cce,
		    SMB2_CREATE_QUERY_ON_DISK_ID);
		if (rc)
			return (NT_STATUS_INTERNAL_ERROR);
		(void) smb_mbc_poke(mbc, last_top, "l",
		    mbc->chain_offset - last_top);
	}

	if (cc->cc_out_flags & CCTX_AAPL_EXT) {
		cce = &cc->cc_out_aapl;
		/* cc_out_aapl already encoded */

		last_top = mbc->chain_offset;
		rc = smb2_encode_create_ctx_elem(mbc, cce,
		    SMB2_CREATE_CTX_AAPL);
		if (rc)
			return (NT_STATUS_INTERNAL_ERROR);
		(void) smb_mbc_poke(mbc, last_top, "l",
		    mbc->chain_offset - last_top);
	}

	if (cc->cc_out_flags & CCTX_REQUEST_LEASE) {
		cce = &cc->cc_out_req_lease;

		cce->cce_mbc.max_bytes = cce->cce_len = 32;
		(void) smb_mbc_encodef(&cce->cce_mbc, "#cllq",
		    UUID_LEN,			/* # */
		    op->lease_key,		/* c */
		    op->lease_state,		/* l */
		    op->lease_flags,		/* l */
		    0LL);			/* q */
		if (op->lease_version == 2) {
			cce->cce_mbc.max_bytes = cce->cce_len = 52;
			(void) smb_mbc_encodef(&cce->cce_mbc,
			    "#cw..",
			    UUID_LEN,
			    op->parent_lease_key,
			    op->lease_epoch);
		}

		last_top = mbc->chain_offset;
		rc = smb2_encode_create_ctx_elem(mbc, cce,
		    SMB2_CREATE_REQUEST_LEASE);
		if (rc)
			return (NT_STATUS_INTERNAL_ERROR);
		(void) smb_mbc_poke(mbc, last_top, "l",
		    mbc->chain_offset - last_top);
	}

	if (cc->cc_out_flags & CCTX_DH_REQUEST) {
		cce = &cc->cc_out_dh_request;

		cce->cce_mbc.max_bytes = cce->cce_len = 8;
		(void) smb_mbc_encodef(&cce->cce_mbc, "q", 0LL);

		last_top = mbc->chain_offset;
		rc = smb2_encode_create_ctx_elem(mbc, cce,
		    SMB2_CREATE_DURABLE_HANDLE_REQUEST);
		if (rc)
			return (NT_STATUS_INTERNAL_ERROR);
		(void) smb_mbc_poke(mbc, last_top, "l",
		    mbc->chain_offset - last_top);
	}

	if (cc->cc_out_flags & CCTX_DH_REQUEST_V2) {
		cce = &cc->cc_out_dh_request_v2;

		cce->cce_mbc.max_bytes = cce->cce_len = 8;
		(void) smb_mbc_encodef(&cce->cce_mbc, "ll",
		    op->dh_timeout, op->dh_v2_flags);

		last_top = mbc->chain_offset;
		rc = smb2_encode_create_ctx_elem(mbc, cce,
		    SMB2_CREATE_DURABLE_HANDLE_REQUEST_V2);
		if (rc)
			return (NT_STATUS_INTERNAL_ERROR);
		(void) smb_mbc_poke(mbc, last_top, "l",
		    mbc->chain_offset - last_top);
	}

	if (last_top >= 0)
		(void) smb_mbc_poke(mbc, last_top, "l", 0);

	return (0);
}

static int
smb2_encode_create_ctx_elem(mbuf_chain_t *out_mbc,
    smb2_create_ctx_elem_t *cce, uint32_t id)
{
	union {
		uint32_t i;
		char ch[4];
	} cc_name;
	int rc;

	/* as above */
	cc_name.i = htonl(id);

	/*
	 * This is the header, per [MS-SMB2] 2.2.13.2
	 * Sorry about the fixed offsets.  We know we'll
	 * layout the data part as [name, payload] and
	 * name is a fixed length, so this easy.
	 * The final layout looks like this:
	 *	a: this header (16 bytes)
	 *	b: the name (4 bytes, 4 pad)
	 *	c: the payload (variable)
	 *	d: padding (to align 8)
	 *
	 * Note that "Next elem." is filled in later.
	 */
	rc = smb_mbc_encodef(
	    out_mbc, "lwwwwl",
	    0,		/* Next offset	l */
	    16,		/* NameOffset	w */
	    4,		/* NameLength	w */
	    0,		/* Reserved	w */
	    24,		/* DataOffset	w */
	    cce->cce_len); /* DataLen	l */
	if (rc)
		return (rc);

	/*
	 * Now the "name" and payload.
	 */
	rc = smb_mbc_encodef(
	    out_mbc, "4c4.#C",
	    cc_name.ch,		/* 4c4. */
	    cce->cce_len,	/* # */
	    &cce->cce_mbc);	/* C */

	(void) smb_mbc_put_align(out_mbc, 8);

	return (rc);
}

static void
smb2_free_create_ctx(smb2_create_ctx_t *cc)
{
	smb2_create_ctx_elem_t *cce;

	if (cc->cc_out_flags & CCTX_QUERY_MAX_ACCESS) {
		cce = &cc->cc_out_max_access;
		MBC_FLUSH(&cce->cce_mbc);
	}
	if (cc->cc_out_flags & CCTX_QUERY_ON_DISK_ID) {
		cce = &cc->cc_out_file_id;
		MBC_FLUSH(&cce->cce_mbc);
	}
	if (cc->cc_out_flags & CCTX_AAPL_EXT) {
		cce = &cc->cc_out_aapl;
		MBC_FLUSH(&cce->cce_mbc);
	}
	if (cc->cc_out_flags & CCTX_REQUEST_LEASE) {
		cce = &cc->cc_out_req_lease;
		MBC_FLUSH(&cce->cce_mbc);
	}
	if (cc->cc_out_flags & CCTX_DH_REQUEST) {
		cce = &cc->cc_out_dh_request;
		MBC_FLUSH(&cce->cce_mbc);
	}
	if (cc->cc_out_flags & CCTX_DH_REQUEST_V2) {
		cce = &cc->cc_out_dh_request_v2;
		MBC_FLUSH(&cce->cce_mbc);
	}
}