view usr/src/uts/common/fs/smbsrv/smb2_create.c @ 21416:fe2f621c52e3

1841 SMB dtrace provider is incomplete 11180 SMB2 IOCTL FSCTL_LMR_REQUEST_RESILIENCY Returns Incorrect Response Portions contributed by: Joyce McIntosh <joyce.mcintosh@nexenta.com> Reviewed by: Evan Layton <evan.layton@nexenta.com> Reviewed by: Matt Barden <matt.barden@nexenta.com> Reviewed by: Joyce McIntosh <joyce.mcintosh@nexenta.com> Approved by: Richard Lowe <richlowe@richlowe.net>
author Gordon Ross <gwr@nexenta.com>
date Wed, 27 Nov 2013 23:58:32 -0500
parents 541afa21e029
children fa3722dc7122
line wrap: on
line source

/*
 * 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 2017 Nexenta Systems, Inc.  All rights reserved.
 */

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

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

/*
 * 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

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;
	/* 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_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 *);

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;
	uint8_t OplockLevel;
	uint32_t ImpersonationLevel;
	uint64_t SmbCreateFlags;
	uint64_t Reserved4;
	uint16_t NameOffset;
	uint16_t NameLength;
	uint32_t CreateCtxOffset;
	uint32_t CreateCtxLength;
	smb2fid_t smb2fid;
	uint32_t status;
	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_request_complete(sr->fid_ofile);
		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;
		}
	}
	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.
	 */

	/*
	 * Validate the requested oplock level.
	 * Convert the SMB2 oplock level into SMB1 form.
	 */
	switch (op->op_oplock_level) {
	case SMB2_OPLOCK_LEVEL_NONE:
		op->op_oplock_level = SMB_OPLOCK_NONE;
		break;
	case SMB2_OPLOCK_LEVEL_II:
		op->op_oplock_level = SMB_OPLOCK_LEVEL_II;
		break;
	case SMB2_OPLOCK_LEVEL_EXCLUSIVE:
		op->op_oplock_level = SMB_OPLOCK_EXCLUSIVE;
		break;
	case SMB2_OPLOCK_LEVEL_BATCH:
		op->op_oplock_level = SMB_OPLOCK_BATCH;
		break;
	case SMB2_OPLOCK_LEVEL_LEASE:	/* not yet */
	default:
		/* Unknown SMB2 oplock level. */
		status = NT_STATUS_INVALID_PARAMETER;
		goto cmd_done;
	}
	op->op_oplock_levelII = B_TRUE;

	/*
	 * 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 (cctx.cc_in_flags & CCTX_EA_BUFFER) {
		status = NT_STATUS_EAS_NOT_SUPPORTED;
		goto cmd_done;
	}

	/*
	 * ImpersonationLevel (spec. says validate + ignore)
	 * 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;

	/*
	 * 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.
	 */
	smb2fid.persistent = 0;
	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(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;
	}

	/*
	 * 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;
	}

	/*
	 * Convert the negotiated Oplock level back into
	 * SMB2 encoding form.
	 */
	switch (op->op_oplock_level) {
	default:
	case SMB_OPLOCK_NONE:
		OplockLevel = SMB2_OPLOCK_LEVEL_NONE;
		break;
	case SMB_OPLOCK_LEVEL_II:
		OplockLevel = SMB2_OPLOCK_LEVEL_II;
		break;
	case SMB_OPLOCK_EXCLUSIVE:
		OplockLevel = SMB2_OPLOCK_LEVEL_EXCLUSIVE;
		break;
	case SMB_OPLOCK_BATCH:
		OplockLevel = SMB2_OPLOCK_LEVEL_BATCH;
		break;
	}

	/*
	 * Encode the SMB2 Create reply
	 */
	attr = &op->fqi.fq_fattr;
	rc = smb_mbc_encodef(
	    &sr->reply,
	    "wb.lTTTTqqllqqll",
	    89,	/* StructSize */	/* w */
	    OplockLevel,		/* 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;
			/* no input data for this */
			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;
		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_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;

		}

	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 (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)
	 *
	 * 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 */

	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);
	}
}