--- /dev/null	Mon Aug  5 17:26:11 2019
+++ new/usr/src/uts/common/fs/smbclnt/netsmb/smb2_smb.c	Mon Aug  5 17:26:11 2019
@@ -0,0 +1,1325 @@
+/*
+ * Copyright (c) 2011 - 2013 Apple Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ *
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+/*
+ * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kmem.h>
+#include <sys/proc.h>
+#include <sys/lock.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/random.h>
+#include <sys/note.h>
+#include <sys/errno.h>
+#include <sys/cmn_err.h>
+
+#include <smb/ntaccess.h>
+#include <smb/winioctl.h>
+
+#include <netsmb/smb_osdep.h>
+
+#include <netsmb/smb.h>
+#include <netsmb/smb2.h>
+#include <netsmb/smb_conn.h>
+#include <netsmb/smb_subr.h>
+#include <netsmb/smb_tran.h>
+#include <netsmb/smb_rq.h>
+#include <netsmb/smb2_rq.h>
+
+#define	NDIALECTS	1
+static const uint16_t smb2_dialects[1] = {
+	SMB2_DIALECT_0210
+};
+
+/* Optional capabilities we advertise (none yet). */
+uint32_t smb2_clnt_caps = 0;
+
+/* How many credits to ask for during ssn. setup. */
+uint16_t smb2_ss_req_credits = 64;
+
+/*
+ * Default timeout values, all in seconds.
+ * Make these tunable (only via mdb for now).
+ */
+int smb2_timo_notice = 15;
+int smb2_timo_default = 30;
+int smb2_timo_logon = 45;
+int smb2_timo_open = 45;
+int smb2_timo_read = 45;
+int smb2_timo_write = 60;
+int smb2_timo_append = 90;
+
+/*
+ * This is a special handler for the odd SMB1-to-SMB2 negotiate
+ * response, where an SMB1 request gets an SMB2 response.
+ *
+ * Unlike most parse functions here, this needs to parse both
+ * the SMB2 header and the nego. response body.  Note that
+ * the only "SMB2" dialect our SMB1 negotiate offered was
+ * { SMB_DIALECT_SMB2_FF, "SMB 2.???"} so the only valid
+ * SMB2 dialect we should get is: SMB2_DIALECT_02ff
+ */
+int
+smb2_parse_smb1nego_resp(struct smb_rq *rqp)
+{
+	struct smb_vc *vcp = rqp->sr_vc;
+	struct smb_sopt *sp = &vcp->vc_sopt;
+	struct mdchain *mdp;
+	uint16_t length = 0;
+	int error;
+
+	/* Get pointer to response data */
+	smb_rq_getreply(rqp, &mdp);
+
+	error = smb2_rq_parsehdr(rqp);
+	if (error != 0)
+		return (error);
+
+	/*
+	 * Parse SMB 2/3 Negotiate Response
+	 * We are already pointing to begining of Response data
+	 */
+
+	/* Check structure size is 65 */
+	md_get_uint16le(mdp, &length);
+	if (length != 65)
+		return (EBADRPC);
+
+	/* Get Security Mode */
+	md_get_uint16le(mdp, &sp->sv2_security_mode);
+
+	/* Get Dialect. */
+	error = md_get_uint16le(mdp, &sp->sv2_dialect);
+	if (error != 0)
+		return (error);
+
+	/* What dialect did we get? */
+	if (sp->sv2_dialect != SMB2_DIALECT_02ff) {
+		SMBERROR("Unknown dialect 0x%x\n", sp->sv2_dialect);
+		return (EINVAL);
+	}
+	/* Set our (internal) SMB1 dialect also. */
+	sp->sv_proto = SMB_DIALECT_SMB2_FF;
+
+	/*
+	 * This request did not go through smb2_iod_addrq and
+	 * smb2_iod_process() so the SMB2 message ID state is
+	 * behind what we need it to be.  Fix that.
+	 */
+	vcp->vc2_next_message_id = 1;
+	vcp->vc2_limit_message_id = 2;
+
+	/*
+	 * Skip parsing the rest.  We'll get a normal
+	 * SMB2 negotiate next and do negotiate then.
+	 */
+	return (0);
+}
+
+int
+smb2_smb_negotiate(struct smb_vc *vcp, struct smb_cred *scred)
+{
+	smb_sopt_t *sp = &vcp->vc_sopt;
+	smbioc_ssn_work_t *wk = &vcp->vc_work;
+	struct smb_rq *rqp = NULL;
+	struct mbchain *mbp = NULL;
+	struct mdchain *mdp = NULL;
+	uint16_t ndialects = NDIALECTS;
+	boolean_t will_sign = B_FALSE;
+	uint16_t length = 0;
+	uint16_t security_mode;
+	uint16_t sec_buf_off;
+	uint16_t sec_buf_len;
+	int err, i;
+
+	/*
+	 * Compute security mode
+	 */
+	if (vcp->vc_vopt & SMBVOPT_SIGNING_REQUIRED) {
+		security_mode = SMB2_NEGOTIATE_SIGNING_REQUIRED;
+	} else {
+		security_mode = SMB2_NEGOTIATE_SIGNING_ENABLED;
+	}
+
+	err = smb_rq_alloc(VCTOCP(vcp), SMB2_NEGOTIATE, scred, &rqp);
+	if (err)
+		return (err);
+
+	/*
+	 * Build the SMB2 negotiate request.
+	 */
+	smb_rq_getrequest(rqp, &mbp);
+	mb_put_uint16le(mbp, 36);		/* Struct Size */
+	mb_put_uint16le(mbp, ndialects);	/* Dialect Count */
+	mb_put_uint16le(mbp, security_mode);
+	mb_put_uint16le(mbp, 0);		/*  Reserved */
+	mb_put_uint32le(mbp, smb2_clnt_caps);
+	mb_put_mem(mbp, vcp->vc_cl_guid, 16, MB_MSYSTEM);
+	mb_put_uint64le(mbp, 0);		/* Start Time */
+	for (i = 0; i < ndialects; i++) {	/* Dialects */
+		mb_put_uint16le(mbp, smb2_dialects[i]);
+	}
+
+	/*
+	 * Do the OTW call.
+	 */
+	err = smb2_rq_internal(rqp, smb2_timo_default);
+	if (err) {
+		goto errout;
+	}
+	/* Should only get status success. */
+	if (rqp->sr_error != NT_STATUS_SUCCESS) {
+		err = ENOTSUP;
+		goto errout;
+	}
+
+	/*
+	 * Decode the negotiate response
+	 */
+	smb_rq_getreply(rqp, &mdp);
+
+	md_get_uint16le(mdp, &length);	/* Struct size */
+	if (length != 65) {
+		err = EBADRPC;
+		goto errout;
+	}
+
+	md_get_uint16le(mdp, &sp->sv2_security_mode);
+	md_get_uint16le(mdp, &sp->sv2_dialect);
+	md_get_uint16le(mdp, NULL);	/* reserved */
+	md_get_mem(mdp, sp->sv2_guid, 16, MB_MSYSTEM);
+	md_get_uint32le(mdp, &sp->sv2_capabilities);
+	md_get_uint32le(mdp, &sp->sv2_maxtransact);
+	md_get_uint32le(mdp, &sp->sv2_maxread);
+	md_get_uint32le(mdp, &sp->sv2_maxwrite);
+	md_get_uint64le(mdp, NULL);	/* curr_time */
+	md_get_uint64le(mdp, NULL);	/* boot_time */
+
+	/* Get Security Blob offset and length */
+	md_get_uint16le(mdp, &sec_buf_off);
+	err = md_get_uint16le(mdp, &sec_buf_len);
+	if (err != 0)
+		goto errout;
+	md_get_uint32le(mdp, NULL);	/* reserved */
+
+	/*
+	 * Security buffer offset is from the beginning of SMB 2 Header
+	 * Calculate how much further we have to go to get to it.
+	 * Current offset is: SMB2_HDRLEN + 64
+	 */
+	if (sec_buf_len != 0) {
+		int skip = (int)sec_buf_off - (SMB2_HDRLEN + 64);
+		if (skip < 0) {
+			err = EBADRPC;
+			goto errout;
+		}
+		if (skip > 0) {
+			md_get_mem(mdp, NULL, skip, MB_MSYSTEM);
+		}
+
+		/*
+		 * Copy the security blob out to user space.
+		 * Buffer addr,size in vc_auth_rbuf,rlen
+		 */
+		if (wk->wk_u_auth_rlen < sec_buf_len) {
+			SMBSDEBUG("vc_auth_rbuf too small");
+			/* Give caller required size. */
+			wk->wk_u_auth_rlen = sec_buf_len;
+			err = EMSGSIZE;
+			goto errout;
+		}
+		wk->wk_u_auth_rlen = sec_buf_len;
+		err = md_get_mem(mdp, wk->wk_u_auth_rbuf.lp_ptr,
+				 sec_buf_len, MB_MUSER);
+		if (err) {
+			goto errout;
+		}
+	}
+
+	/*
+	 * Decoded everything.  Now decisions.
+	 */
+
+	/*
+	 * Turn on signing if either Server or client requires it,
+	 * except: anonymous sessions can't sign.
+	 */
+	if ((sp->sv2_security_mode & SMB2_NEGOTIATE_SIGNING_REQUIRED) ||
+	    (vcp->vc_vopt & SMBVOPT_SIGNING_REQUIRED))
+		will_sign = B_TRUE;
+	if (vcp->vc_vopt & SMBVOPT_ANONYMOUS)
+		will_sign = B_FALSE;
+	SMBSDEBUG("Security signatures: %d", (int)will_sign);
+	if (will_sign)
+		vcp->vc_flags |= SMBV_SIGNING;
+
+	/*
+	 * ToDo - too many places are looking at sv_caps, so for now
+	 * set the SMB1 capabilities too.  Later we should use the
+	 * sv2_capabilities for SMB 2+.
+	 */
+	sp->sv_caps =  (SMB_CAP_UNICODE |
+			SMB_CAP_LARGE_FILES |
+			SMB_CAP_STATUS32 |
+			SMB_CAP_LARGE_READX |
+			SMB_CAP_LARGE_WRITEX |
+			SMB_CAP_EXT_SECURITY);
+	if (sp->sv2_capabilities & SMB2_CAP_DFS)
+		sp->sv_caps |= SMB_CAP_DFS;
+
+	/*
+	 * A few sanity checks on what we received,
+	 * becuse we will send these in ssnsetup.
+	 *
+	 * Maximum outstanding requests (we care),
+	 * and Max. VCs (we only use one).  Also,
+	 * MaxBufferSize lower limit per spec.
+	 */
+	if (sp->sv2_maxread < 0x8000) {
+		SMBSDEBUG("maxread too small\n");
+		err = ENOTSUP;
+		goto errout;
+	}
+	if (sp->sv2_maxwrite < 0x8000) {
+		SMBSDEBUG("maxwrite too small\n");
+		err = ENOTSUP;
+		goto errout;
+	}
+	if (sp->sv2_maxtransact < 0x4000) {
+		SMBSDEBUG("maxtransact too small\n");
+		err = ENOTSUP;
+		goto errout;
+	}
+
+	/* Here too, fill SMB1 fields */
+	vcp->vc_rxmax = sp->sv2_maxread;
+	vcp->vc_wxmax = sp->sv2_maxwrite;
+	vcp->vc_txmax = sp->sv2_maxtransact;
+
+	smb_rq_done(rqp);
+	return (0);
+
+errout:
+	smb_rq_done(rqp);
+	if (err == 0)
+		err = EBADRPC;
+	return (err);
+}
+
+int
+smb2_smb_ssnsetup(struct smb_vc *vcp, struct smb_cred *scred)
+{
+	// smb_sopt_t *sv = &vcp->vc_sopt;
+	smbioc_ssn_work_t *wk = &vcp->vc_work;
+	struct smb_rq *rqp = NULL;
+	struct mbchain *mbp = NULL;
+	struct mdchain *mdp = NULL;
+	char *sb;
+	int err, ret;
+	uint16_t sblen;
+	uint16_t length = 0;
+	uint16_t session_flags;
+	uint16_t sec_buf_off;
+	uint16_t sec_buf_len;
+	uint8_t security_mode;
+
+	/*
+	 * Compute security mode
+	 */
+	if (vcp->vc_vopt & SMBVOPT_SIGNING_REQUIRED) {
+		security_mode = SMB2_NEGOTIATE_SIGNING_REQUIRED;
+	} else {
+		security_mode = SMB2_NEGOTIATE_SIGNING_ENABLED;
+	}
+
+	sb = wk->wk_u_auth_wbuf.lp_ptr;
+	sblen = (uint16_t)wk->wk_u_auth_wlen;
+
+	err = smb_rq_alloc(VCTOCP(vcp), SMB2_SESSION_SETUP, scred, &rqp);
+	if (err != 0) {
+		ret = err;
+		goto out;
+	}
+
+	/*
+	 * Always ask for some credits. The server usually will
+	 * only grant these credits once we've authenticated.
+	 */
+	rqp->sr2_creditsrequested = smb2_ss_req_credits;
+
+	/*
+	 * Build the SMB Session Setup request.
+	 */
+	smb_rq_getrequest(rqp, &mbp);
+
+	mb_put_uint16le(mbp, 25);	/* Struct size */
+	mb_put_uint8(mbp, 0);		/* VcNumber */
+	mb_put_uint8(mbp, security_mode);
+	mb_put_uint32le(mbp, smb2_clnt_caps);	/* Capabilities */
+	mb_put_uint32le(mbp, 0);	/* Channel - always 0 */
+
+	/*
+	 * Security buffer offset and length.  Normally would use
+	 * ptr = mb_reserve() and fill in later, but since only a
+	 * small amount of fixed-size stuff follows (12 bytes)
+	 * we can just compute the offset now.
+	 */
+	mb_put_uint16le(mbp, mbp->mb_count + 12);
+	mb_put_uint16le(mbp, sblen);
+	mb_put_uint64le(mbp, vcp->vc2_prev_session_id);
+	err = mb_put_mem(mbp, sb, sblen, MB_MUSER);
+	if (err != 0) {
+		ret = err;
+		goto out;
+	}
+
+	/*
+	 * Run the request.  The return value here should be the
+	 * return from this function, unless we fail decoding.
+	 * Note: NT_STATUS_MORE_PROCESSING_REQUIRED is OK, and
+	 * the caller expects EINPROGRESS for that case.
+	 */
+	ret = smb2_rq_internal(rqp, smb2_timo_logon);
+	if (ret != 0)
+		goto out;
+	switch (rqp->sr_error) {
+	case NT_STATUS_SUCCESS:
+		break;
+	case NT_STATUS_MORE_PROCESSING_REQUIRED:
+		/* Keep going, but return... */
+		ret = EINPROGRESS;
+		break;
+	default:
+		ret = EAUTH;
+		goto out;
+	}
+
+	/*
+	 * After the first Session Setup Response,
+	 * save the session ID.
+	 */
+	if (vcp->vc2_session_id == 0)
+		vcp->vc2_session_id = rqp->sr2_rspsessionid;
+
+	/*
+	 * Decode the session setup response
+	 */
+	smb_rq_getreply(rqp, &mdp);
+
+	md_get_uint16le(mdp, &length);	/* Struct size */
+	if (length != 9) {
+		ret = EBADRPC;
+		goto out;
+	}
+
+	md_get_uint16le(mdp, &session_flags);
+	md_get_uint16le(mdp, &sec_buf_off);
+	err = md_get_uint16le(mdp, &sec_buf_len);
+	if (err != 0) {
+		ret = err;
+		goto out;
+	}
+
+	/*
+	 * Security buffer offset is from the beginning of SMB 2 Header
+	 * Calculate how much further we have to go to get to it.
+	 * Current offset is: SMB2_HDRLEN + 8
+	 */
+	if (sec_buf_len != 0) {
+		int skip = (int)sec_buf_off - (SMB2_HDRLEN + 8);
+		if (skip < 0) {
+			ret = EBADRPC;
+			goto out;
+		}
+		if (skip > 0) {
+			md_get_mem(mdp, NULL, skip, MB_MSYSTEM);
+		}
+
+		/*
+		 * Copy the security blob out to user space.
+		 * Buffer addr,size in vc_auth_rbuf,rlen
+		 */
+		if (wk->wk_u_auth_rlen < sec_buf_len) {
+			SMBSDEBUG("vc_auth_rbuf too small");
+			/* Give caller required size. */
+			wk->wk_u_auth_rlen = sec_buf_len;
+			ret = EMSGSIZE;
+			goto out;
+		}
+		wk->wk_u_auth_rlen = sec_buf_len;
+		err = md_get_mem(mdp, wk->wk_u_auth_rbuf.lp_ptr,
+				 sec_buf_len, MB_MUSER);
+		if (err != 0) {
+			ret = err;
+			goto out;
+		}
+	}
+
+out:
+	if (err != 0 && err != EINPROGRESS) {
+		/* Session ID no longer valid. */
+		vcp->vc2_session_id = 0;
+	}
+	if (rqp)
+		smb_rq_done(rqp);
+
+	return (ret);
+}
+
+int
+smb2_smb_logoff(struct smb_vc *vcp, struct smb_cred *scred)
+{
+	struct smb_rq *rqp;
+	struct mbchain *mbp;
+	int error;
+
+	if (vcp->vc2_session_id == 0)
+		return (0);
+
+	error = smb_rq_alloc(VCTOCP(vcp), SMB2_LOGOFF, scred, &rqp);
+	if (error)
+		return (error);
+
+	/*
+	 * Fill in Logoff part
+	 */
+	smb_rq_getrequest(rqp, &mbp);
+	mb_put_uint16le(mbp, 4);	/* Struct size */
+	mb_put_uint16le(mbp, 0);	/* Reserved */
+
+	/*
+	 * Run this with a relatively short timeout. (5 sec.)
+	 * We don't really care about the result here.
+	 * Also, don't reconnect for this, of course!
+	 */
+	rqp->sr_flags |= SMBR_NORECONNECT;
+	error = smb2_rq_internal(rqp, 5);
+	smb_rq_done(rqp);
+	return (error);
+}
+
+int
+smb2_smb_treeconnect(struct smb_share *ssp, struct smb_cred *scred)
+{
+	struct smb_vc *vcp;
+	struct smb_rq *rqp = NULL;
+	struct mbchain *mbp;
+	struct mdchain *mdp;
+	char *unc_name = NULL;
+	int error, unc_len;
+	uint16_t plen, *plenp;
+	uint16_t options = 0;
+	uint_t cnt0;
+	uint32_t net_stype;
+	uint16_t structure_size = 0;
+	uint8_t smb2stype;
+
+	vcp = SSTOVC(ssp);
+
+	/*
+	 * Make this a "VC-level" request, so it will have
+	 * rqp->sr_share == NULL, and smb_iod_sendrq()
+	 * will send it with TID = SMB_TID_UNKNOWN
+	 *
+	 * This also serves to bypass the wait for
+	 * share state changes, which this call is
+	 * trying to carry out.
+	 */
+	error = smb_rq_alloc(VCTOCP(vcp), SMB2_TREE_CONNECT, scred, &rqp);
+	if (error)
+		return (error);
+
+	/*
+	 * Build the UNC name, i.e. "//server/share"
+	 * but with backslashes of course.
+	 * size math: three slashes, one null.
+	 */
+	unc_len = 4 + strlen(vcp->vc_srvname) + strlen(ssp->ss_name);
+	unc_name = kmem_alloc(unc_len, KM_SLEEP);
+	(void) snprintf(unc_name, unc_len, "\\\\%s\\%s",
+	    vcp->vc_srvname, ssp->ss_name);
+	SMBSDEBUG("unc_name: \"%s\"", unc_name);
+
+	/*
+	 * Build the request.
+	 */
+	mbp = &rqp->sr_rq;
+
+	mb_put_uint16le(mbp, 9);	/* Struct size */
+	mb_put_uint16le(mbp, 0);	/* Reserved */
+	mb_put_uint16le(mbp, 72);	/* Path Offset */
+
+	/*
+	 * Fill in path length after we put the string, so we know
+	 * the length after conversion from UTF-8 to UCS-2.
+	 */
+	plenp = mb_reserve(mbp, 2);
+	cnt0 = mbp->mb_count;
+
+	/* UNC resource name (without the null) */
+	error = smb_put_dmem(mbp, vcp, unc_name, unc_len - 1,
+	    SMB_CS_NONE, NULL);
+	if (error)
+		goto out;
+
+	/* Now go back and fill in the path length. */
+	plen = (uint16_t)(mbp->mb_count - cnt0);
+	*plenp = htoles(plen);
+
+	/*
+	 * Run the request.
+	 *
+	 * Using NOINTR_RECV because we don't want to risk
+	 * missing a successful tree connect response,
+	 * which would "leak" Tree IDs.
+	 */
+	rqp->sr_flags |= SMBR_NOINTR_RECV;
+	error = smb2_rq_simple(rqp);
+	SMBSDEBUG("%d\n", error);
+	if (error) {
+		/*
+		 * If we get the server name wrong, i.e. due to
+		 * mis-configured name services, this will be
+		 * NT_STATUS_DUPLICATE_NAME.  Log this error.
+		 */
+		SMBERROR("(%s) failed, status=0x%x",
+		    unc_name, rqp->sr_error);
+		goto out;
+	}
+
+	/*
+	 * Parse the tree connect response
+	 */
+	smb_rq_getreply(rqp, &mdp);
+
+	/* Check structure size is 16 */
+	md_get_uint16le(mdp, &structure_size);
+	if (structure_size != 16) {
+		error = EBADRPC;
+		goto out;
+	}
+
+	md_get_uint8(mdp, &smb2stype);
+	md_get_uint8(mdp, NULL);	/* reserved */
+	md_get_uint32le(mdp, &ssp->ss2_share_flags);
+	md_get_uint32le(mdp, &ssp->ss2_share_caps);
+	error = md_get_uint32le(mdp, NULL);	/* maxAccessRights */
+	if (error)
+		goto out;
+
+	/*
+	 * Convert SMB2 share type to NetShareEnum share type
+	 */
+	switch (smb2stype) {
+	case SMB2_SHARE_TYPE_DISK:
+		net_stype = STYPE_DISKTREE;
+		break;
+	case SMB2_SHARE_TYPE_PIPE:
+		net_stype = STYPE_IPC;
+		break;
+	case SMB2_SHARE_TYPE_PRINT:
+		net_stype = STYPE_PRINTQ;
+		break;
+	default:
+		net_stype = STYPE_UNKNOWN;
+		break;
+	}
+	ssp->ss_type = net_stype;
+
+	/*
+	 * Map SMB 2/3 capabilities to SMB 1 options,
+	 * for common code that looks there.
+	 */
+	if (ssp->ss2_share_caps & SMB2_SHARE_CAP_DFS)
+		options |= SMB_SHARE_IS_IN_DFS;
+
+	/* Update share state */
+	SMB_SS_LOCK(ssp);
+	ssp->ss2_tree_id = rqp->sr2_rsptreeid;
+	ssp->ss_vcgenid = vcp->vc_genid;
+	ssp->ss_options = options;
+	ssp->ss_flags |= SMBS_CONNECTED;
+	SMB_SS_UNLOCK(ssp);
+
+out:
+	if (unc_name)
+		kmem_free(unc_name, unc_len);
+	smb_rq_done(rqp);
+	return (error);
+}
+
+int
+smb2_smb_treedisconnect(struct smb_share *ssp, struct smb_cred *scred)
+{
+	struct smb_vc *vcp;
+	struct smb_rq *rqp;
+	struct mbchain *mbp;
+	int error;
+
+	if (ssp->ss2_tree_id == SMB2_TID_UNKNOWN)
+		return (0);
+
+	/*
+	 * Build this as a "VC-level" request, so it will
+	 * avoid testing the _GONE flag on the share,
+	 * which has already been set at this point.
+	 * Add the share pointer "by hand" below, so
+	 * smb_iod_sendrq will plug in the TID.
+	 */
+	vcp = SSTOVC(ssp);
+	error = smb_rq_alloc(VCTOCP(vcp), SMB2_TREE_DISCONNECT, scred, &rqp);
+	if (error)
+		return (error);
+	rqp->sr_share = ssp; /* See "by hand" above. */
+
+	/*
+	 * Fill in SMB2 Tree Disconnect part
+	 */
+	smb_rq_getrequest(rqp, &mbp);
+	mb_put_uint16le(mbp, 4);	/* Struct size */
+	mb_put_uint16le(mbp, 0);	/* Reserved */
+
+	/*
+	 * Run this with a relatively short timeout. (5 sec.)
+	 * We don't really care about the result here, but we
+	 * do need to make sure we send this out, or we could
+	 * "leak" active tree IDs on interrupt or timeout.
+	 * The NOINTR_SEND flag makes this request immune to
+	 * interrupt or timeout until the send is done.
+	 * Also, don't reconnect for this, of course!
+	 */
+	rqp->sr_flags |= (SMBR_NOINTR_SEND | SMBR_NORECONNECT);
+	error = smb2_rq_simple_timed(rqp, 5);
+
+	smb_rq_done(rqp);
+
+	/* Whether we get an error or not... */
+	ssp->ss2_tree_id = SMB2_TID_UNKNOWN;
+
+	return (error);
+}
+
+/*
+ * Put the name, first skipping a leading slash.
+ */
+static int
+put_name_skip_slash(struct mbchain *mbp, struct mbchain *name_mbp)
+{
+	mblk_t *m;
+
+	if (name_mbp == NULL)
+		return (0);
+	m = name_mbp->mb_top;
+	if (m == NULL)
+		return (0);
+
+	/* Use a dup of the message to leave the passed one untouched. */
+	m = dupmsg(m);
+	if (m == NULL)
+		return (ENOSR);
+
+	if (MBLKL(m) >= 2 &&
+	    m->b_rptr[0] == '\\' &&
+	    m->b_rptr[1] == '\0')
+		m->b_rptr += 2;
+
+	return (mb_put_mbuf(mbp, m));
+}
+
+/*
+ * Modern create/open of file or directory.
+ *
+ * The passed name is a full path relative to the share root.
+ * Callers prepare paths with a leading slash (backslash)
+ * because that's what SMB1 expected.  SMB2 does not allow the
+ * leading slash here.  To make life simpler for callers skip a
+ * leading slash here.  That allows callers use use common logic
+ * for building paths without needing to know if the connection
+ * is using SMB1 or SMB2 (just build paths with a leading slash).
+ */
+int
+smb2_smb_ntcreate(
+	struct smb_share *ssp,
+	struct mbchain	*name_mb,
+	struct mbchain	*cctx_in,
+	struct mdchain	*cctx_out,
+	uint32_t cr_flags,	/* create flags */
+	uint32_t req_acc,	/* requested access */
+	uint32_t efa,		/* ext. file attrs (DOS attr +) */
+	uint32_t share_acc,
+	uint32_t open_disp,	/* open disposition */
+	uint32_t createopt,	/* NTCREATEX_OPTIONS_ */
+	uint32_t impersonate,	/* NTCREATEX_IMPERSONATION_... */
+	struct smb_cred *scrp,
+	smb2fid_t *fidp,	/* returned FID */
+	uint32_t *cr_act_p,	/* optional create action */
+	struct smbfattr *fap)	/* optional attributes */
+{
+	struct smbfattr fa;
+	struct smb_rq *rqp;
+	struct mbchain *mbp;
+	struct mdchain *mdp;
+	uint16_t *name_offp;
+	uint16_t *name_lenp;
+	uint32_t *cctx_offp;
+	uint32_t *cctx_lenp;
+	uint32_t rcc_off, rcc_len;
+	smb2fid_t smb2_fid;
+	uint64_t llongint;
+	uint32_t longint, createact;
+	uint_t off, len;
+	int error;
+	uint16_t StructSize = 57;	// [MS-SMB2]
+
+	bzero(&fa, sizeof (fa));
+
+	error = smb_rq_alloc(SSTOCP(ssp), SMB2_CREATE, scrp, &rqp);
+	if (error)
+		return (error);
+
+	/*
+	 * Todo: Assemble creat contexts (if needed)
+	 * into an mbchain.
+	 */
+
+	/*
+	 * Build the SMB 2/3 Create Request
+	 */
+	smb_rq_getrequest(rqp, &mbp);
+	mb_put_uint16le(mbp, StructSize);
+	mb_put_uint8(mbp, 0);				/* Security flags */
+	mb_put_uint8(mbp, SMB2_OPLOCK_LEVEL_NONE);	/* Oplock level */
+	mb_put_uint32le(mbp, impersonate);		/* Impersonation Level */
+	mb_put_uint64le(mbp, cr_flags);
+	mb_put_uint64le(mbp, 0);			/* Reserved */
+	mb_put_uint32le(mbp, req_acc);
+	mb_put_uint32le(mbp, efa);			/* File attributes */
+	mb_put_uint32le(mbp, share_acc);		/* Share access */
+	mb_put_uint32le(mbp, open_disp);		/* Create disposition */
+	mb_put_uint32le(mbp, createopt);		/* Create options */
+
+	name_offp = mb_reserve(mbp, 2);			/* Name offset */
+	name_lenp = mb_reserve(mbp, 2);			/* Name len */
+
+	cctx_offp = mb_reserve(mbp, 4);			/* Context offset */
+	cctx_lenp = mb_reserve(mbp, 4);			/* Context len */
+
+	/*
+	 * Put the file name, which is provided in an mbchain.
+	 * If there's a leading slash, skip it (see above).
+	 */
+	off = mbp->mb_count;
+	*name_offp = htoles((uint16_t)off);
+	error = put_name_skip_slash(mbp, name_mb);
+	if (error)
+		goto out;
+	len = mbp->mb_count - off;
+	*name_lenp = htoles((uint16_t)len);
+
+	/*
+	 * Now the create contexts (if provided)
+	 */
+	if (cctx_in != NULL) {
+		off = mbp->mb_count;
+		*cctx_offp = htolel((uint32_t)off);
+		mb_put_mbchain(mbp, cctx_in);
+		len = mbp->mb_count - off;
+		*cctx_lenp = htolel((uint32_t)len);
+	} else {
+		*cctx_offp = 0;
+		*cctx_lenp = 0;
+	}
+
+	/*
+	 * If we didn't put any variable-sized data, we'll have
+	 * put exactly 56 bytes of data, and we need to pad out
+	 * this request to the 57 bytes StructSize indicated.
+	 */
+	if (mbp->mb_count < (StructSize + SMB2_HDRLEN))
+		mb_put_uint8(mbp, 0);
+
+	/*
+	 * Don't want to risk missing a successful
+	 * open response, or we could "leak" FIDs.
+	 */
+	rqp->sr_flags |= SMBR_NOINTR_RECV;
+	error = smb2_rq_simple_timed(rqp, smb2_timo_open);
+	if (error)
+		goto out;
+
+	/*
+	 * Parse SMB 2/3 Create Response
+	 */
+	smb_rq_getreply(rqp, &mdp);
+
+	/* Check structure size is 89 */
+	error = md_get_uint16le(mdp, &StructSize);
+	if (StructSize != 89) {
+		error = EBADRPC;
+		goto out;
+	}
+
+	md_get_uint8(mdp, NULL);		/* oplock lvl granted */
+	md_get_uint8(mdp, NULL);		/* mbz */
+	md_get_uint32le(mdp, &createact);	/* create_action */
+	md_get_uint64le(mdp, &llongint);	/* creation time */
+	smb_time_NT2local(llongint, &fa.fa_createtime);
+	md_get_uint64le(mdp, &llongint);	/* access time */
+	smb_time_NT2local(llongint, &fa.fa_atime);
+	md_get_uint64le(mdp, &llongint);	/* write time */
+	smb_time_NT2local(llongint, &fa.fa_mtime);
+	md_get_uint64le(mdp, &llongint);	/* change time */
+	smb_time_NT2local(llongint, &fa.fa_ctime);
+	md_get_uint64le(mdp, &llongint);	/* allocation size */
+	fa.fa_allocsz = llongint;
+	md_get_uint64le(mdp, &llongint);	/* EOF position */
+	fa.fa_size = llongint;
+	md_get_uint32le(mdp, &longint);		/* attributes */
+	fa.fa_attr = longint;
+	md_get_uint32le(mdp, NULL);		/* reserved */
+
+	/* Get SMB 2/3 File ID and create user fid to return */
+	md_get_uint64le(mdp, &smb2_fid.fid_persistent);
+	error = md_get_uint64le(mdp, &smb2_fid.fid_volatile);
+	if (error)
+		goto out;
+
+	/* Get Context Offset */
+	error = md_get_uint32le(mdp, &rcc_off);
+	if (error)
+		goto out;
+	/* Get Context Length */
+	error = md_get_uint32le(mdp, &rcc_len);
+	if (error)
+		goto out;
+
+	/*
+	 * If the caller wants the returned create contexts, parse.
+	 * Context offset is from the beginning of SMB 2/3 Header
+	 * Calculate how much further we have to go to get to it.
+	 * Current offset is: SMB2_HDRLEN + 88
+	 */
+	if (rcc_len != 0) {
+		int skip = (int)rcc_off - (SMB2_HDRLEN + 88);
+		if (skip < 0) {
+			error = EBADRPC;
+			goto out;
+		}
+		if (skip > 0) {
+			md_get_mem(mdp, NULL, skip, MB_MSYSTEM);
+		}
+		if (cctx_out != NULL) {
+			mblk_t *m = NULL;
+			error = md_get_mbuf(mdp, rcc_len, &m);
+			if (error)
+				goto out;
+			md_initm(cctx_out, m);
+		}
+	}
+
+out:
+	smb_rq_done(rqp);
+	if (error)
+		return (error);
+
+	*fidp = smb2_fid;
+	if (cr_act_p)
+		*cr_act_p = createact;
+	if (fap)
+		*fap = fa; /* struct copy */
+
+	return (0);
+}
+
+int
+smb2_smb_close(struct smb_share *ssp, smb2fid_t *fid, struct smb_cred *scrp)
+{
+	struct smb_rq *rqp;
+	struct mbchain *mbp;
+	int error;
+
+	error = smb_rq_alloc(SSTOCP(ssp), SMB2_CLOSE, scrp, &rqp);
+	if (error)
+		return (error);
+
+	/*
+	 * Build the SMB 2/3 Close Request
+	 */
+	smb_rq_getrequest(rqp, &mbp);
+	mb_put_uint16le(mbp, 24);		/* Struct size */
+	mb_put_uint16le(mbp, 0);		/* Flags */
+	mb_put_uint32le(mbp, 0);		/* Reserved */
+
+	mb_put_uint64le(mbp, fid->fid_persistent);
+	mb_put_uint64le(mbp, fid->fid_volatile);
+
+	/* Make sure we send, but only if already connected */
+	rqp->sr_flags |= (SMBR_NOINTR_SEND | SMBR_NORECONNECT);
+	error = smb2_rq_simple(rqp);
+	smb_rq_done(rqp);
+	return (error);
+}
+
+int
+smb2_smb_ioctl(
+	struct smb_share *ssp,
+	smb2fid_t *fid,
+	struct mbchain	*data_in,
+	struct mdchain	*data_out,
+	uint32_t *data_out_sz,	/* max / returned */
+	uint32_t ctl_code,
+	struct smb_cred *scrp)
+{
+	struct smb_rq *rqp;
+	struct mbchain *mbp;
+	struct mdchain *mdp;
+	uint32_t *data_in_offp;
+	uint32_t *data_in_lenp;
+	uint32_t data_out_off;
+	uint32_t data_out_len;
+	uint16_t length = 0;
+	uint_t off, len;
+	int error;
+
+	error = smb_rq_alloc(SSTOCP(ssp), SMB2_IOCTL, scrp, &rqp);
+	if (error)
+		return (error);
+
+	/*
+	 * Build the SMB 2 IOCTL Request
+	 */
+	smb_rq_getrequest(rqp, &mbp);
+	mb_put_uint16le(mbp, 57);		/* Struct size */
+	mb_put_uint16le(mbp, 0);		/* Reserved */
+	mb_put_uint32le(mbp, ctl_code);
+
+	mb_put_uint64le(mbp, fid->fid_persistent);
+	mb_put_uint64le(mbp, fid->fid_volatile);
+
+	data_in_offp = mb_reserve(mbp, 4);
+	data_in_lenp = mb_reserve(mbp, 4);
+	mb_put_uint32le(mbp, 0);		/* Max input resp */
+
+	mb_put_uint32le(mbp, 0);		/* Output offset */
+	mb_put_uint32le(mbp, 0);		/* Output count */
+	mb_put_uint32le(mbp, *data_out_sz);
+
+	mb_put_uint32le(mbp, SMB2_IOCTL_IS_FSCTL); /* Flags */
+	mb_put_uint32le(mbp, 0);		/* Reserved2 */
+
+	/*
+	 * Now data_in (if provided)
+	 */
+	if (data_in != NULL) {
+		off = mbp->mb_count;
+		*data_in_offp = htolel((uint32_t)off);
+		mb_put_mbchain(mbp, data_in);
+		len = mbp->mb_count - off;
+		*data_in_lenp = htolel((uint32_t)len);
+	} else {
+		*data_in_offp = 0;
+		*data_in_lenp = 0;
+	}
+
+	/*
+	 * Run the request
+	 */
+	error = smb2_rq_simple_timed(rqp, smb2_timo_default);
+	if (error)
+		goto out;
+
+	/*
+	 * Parse SMB 2 Ioctl Response
+	 */
+	smb_rq_getreply(rqp, &mdp);
+
+	/* Check structure size is 49 */
+	md_get_uint16le(mdp, &length);
+	if (length != 49) {
+		error = EBADRPC;
+		goto out;
+	}
+	md_get_uint16le(mdp, NULL);	/* reserved */
+	md_get_uint32le(mdp, NULL);	/* Get CtlCode */
+	md_get_uint64le(mdp, NULL);	/* fid_persistent */
+	md_get_uint64le(mdp, NULL);	/* fid_volatile */
+	md_get_uint32le(mdp, NULL);	/* Get Input offset */
+	md_get_uint32le(mdp, NULL);	/* Get Input count */
+
+	error = md_get_uint32le(mdp, &data_out_off);
+	if (error)
+		goto out;
+	error = md_get_uint32le(mdp, &data_out_len);
+	if (error)
+		goto out;
+
+	md_get_uint32le(mdp, NULL);	/* Flags */
+	md_get_uint32le(mdp, NULL);	/* reserved */
+
+	/*
+	 * If the caller wants the ioctl output data, parse.
+	 * Current offset is: SMB2_HDRLEN + 48
+	 * Always return the received length.
+	 */
+	*data_out_sz = data_out_len;
+	if (data_out_len != 0) {
+		int skip = (int)data_out_off - (SMB2_HDRLEN + 48);
+		if (skip < 0) {
+			error = EBADRPC;
+			goto out;
+		}
+		if (skip > 0) {
+			md_get_mem(mdp, NULL, skip, MB_MSYSTEM);
+		}
+		if (data_out != NULL) {
+			mblk_t *m = NULL;
+			error = md_get_mbuf(mdp, data_out_len, &m);
+			if (error)
+				goto out;
+			md_initm(data_out, m);
+		}
+	}
+
+out:
+	smb_rq_done(rqp);
+
+	return (error);
+}
+
+int
+smb2_smb_read(smb_fh_t *fhp, uint32_t *lenp,
+	uio_t *uiop, smb_cred_t *scred, int timo)
+{
+	struct smb_share *ssp = FHTOSS(fhp);
+	struct smb_rq *rqp;
+	struct mbchain *mbp;
+	struct mdchain *mdp;
+	int error;
+	uint64_t off64 = uiop->uio_loffset;
+	uint32_t rlen;
+	uint16_t length = 0;
+	uint8_t data_offset;
+
+	error = smb_rq_alloc(SSTOCP(ssp), SMB2_READ, scred, &rqp);
+	if (error)
+		return (error);
+
+	/*
+	 * Build the SMB 2 Read Request
+	 */
+	smb_rq_getrequest(rqp, &mbp);
+	mb_put_uint16le(mbp, 49);		/* Struct size */
+	mb_put_uint16le(mbp, 0);		/* Padding and Reserved */
+
+	mb_put_uint32le(mbp, *lenp);		/* Length of read */
+	mb_put_uint64le(mbp, off64);		/* Offset */
+
+	mb_put_uint64le(mbp, fhp->fh_fid2.fid_persistent);
+	mb_put_uint64le(mbp, fhp->fh_fid2.fid_volatile);
+
+	mb_put_uint32le(mbp, 1);	/* MinCount */
+					/* (only indicates blocking) */
+
+	mb_put_uint32le(mbp, 0);	/* Channel */
+	mb_put_uint32le(mbp, 0);	/* Remaining */
+	mb_put_uint32le(mbp, 0);	/* Channel offset/len */
+	mb_put_uint8(mbp, 0);		/* data "blob" (pad) */
+
+	if (timo == 0)
+		timo = smb2_timo_read;
+	error = smb2_rq_simple_timed(rqp, timo);
+	if (error)
+		goto out;
+
+	/*
+	 * Parse SMB 2 Read Response
+	 */
+	smb_rq_getreply(rqp, &mdp);
+
+	/* Check structure size is 17 */
+	md_get_uint16le(mdp, &length);
+	if (length != 17) {
+		error = EBADRPC;
+		goto out;
+	}
+	md_get_uint8(mdp, &data_offset);
+	md_get_uint8(mdp, NULL);		/* reserved */
+
+	/* Get Data Length read */
+	error = md_get_uint32le(mdp, &rlen);
+	if (error)
+		goto out;
+
+	md_get_uint32le(mdp, NULL);	/* Data Remaining (always 0) */
+	md_get_uint32le(mdp, NULL);	/* Get Reserved2 (always 0) */
+
+	/*
+	 * Data offset is from the beginning of SMB 2/3 Header
+	 * Calculate how much further we have to go to get to it.
+	 */
+	if (data_offset < (SMB2_HDRLEN + 16)) {
+		error = EBADRPC;
+		goto out;
+	}
+	if (data_offset > (SMB2_HDRLEN + 16)) {
+		int skip = data_offset - (SMB2_HDRLEN + 16);
+		md_get_mem(mdp, NULL, skip, MB_MSYSTEM);
+	}
+
+	/*
+	 * Get the data
+	 */
+	if (rlen == 0) {
+		*lenp = rlen;
+		goto out;
+	}
+	/* paranoid */
+	if (rlen > *lenp) {
+		SMBSDEBUG("bad server! rlen %d, len %d\n",
+		    rlen, *lenp);
+		rlen = *lenp;
+	}
+
+	error = md_get_uio(mdp, uiop, rlen);
+	if (error)
+		goto out;
+
+	/* Success */
+	*lenp = rlen;
+
+out:
+	smb_rq_done(rqp);
+	return (error);
+}
+
+int
+smb2_smb_write(smb_fh_t *fhp, uint32_t *lenp,
+	uio_t *uiop, smb_cred_t *scred, int timo)
+{
+	struct smb_share *ssp = FHTOSS(fhp);
+	struct smb_rq *rqp;
+	struct mbchain *mbp;
+	struct mdchain *mdp;
+	int error;
+	uint64_t off64 = uiop->uio_loffset;
+	uint32_t rlen;
+	uint16_t data_offset;
+	uint16_t length = 0;
+
+	error = smb_rq_alloc(SSTOCP(ssp), SMB2_WRITE, scred, &rqp);
+	if (error)
+		return (error);
+
+	/*
+	 * Build the SMB 2 Write Request
+	 */
+	smb_rq_getrequest(rqp, &mbp);
+	mb_put_uint16le(mbp, 49);		/* Struct size */
+	data_offset = SMB2_HDRLEN + 48;
+	mb_put_uint16le(mbp, data_offset);	/* Data Offset */
+	mb_put_uint32le(mbp, *lenp);		/* Length of write */
+	mb_put_uint64le(mbp, off64);		/* Offset */
+
+	mb_put_uint64le(mbp, fhp->fh_fid2.fid_persistent);
+	mb_put_uint64le(mbp, fhp->fh_fid2.fid_volatile);
+
+	mb_put_uint32le(mbp, 0);		/* Channel */
+	mb_put_uint32le(mbp, 0);		/* Remaining */
+	mb_put_uint32le(mbp, 0);		/* Channel offset/len */
+	mb_put_uint32le(mbp, 0);		/* Write flags */
+
+	error = mb_put_uio(mbp, uiop, *lenp);
+	if (error)
+		goto out;
+
+	if (timo == 0)
+		timo = smb2_timo_write;
+	error = smb2_rq_simple_timed(rqp, timo);
+	if (error)
+		goto out;
+
+	/*
+	 * Parse SMB 2/3 Write Response
+	 */
+	smb_rq_getreply(rqp, &mdp);
+
+	/* Check structure size is 17 */
+	md_get_uint16le(mdp, &length);
+	if (length != 17) {
+		error = EBADRPC;
+		goto out;
+	}
+
+	md_get_uint16le(mdp, NULL);    /* Get Reserved */
+
+	/* Get Data Length written */
+	error = md_get_uint32le(mdp, &rlen);
+	if (error)
+		goto out;
+
+	/* Get Data Remaining (always 0) */
+	md_get_uint32le(mdp, NULL);
+
+	/* Get Reserved2 (always 0) */
+	md_get_uint32le(mdp, NULL);
+
+	/* Success */
+	*lenp = rlen;
+
+out:
+	smb_rq_done(rqp);
+	return (error);
+}
+
+/*
+ * Note: the IOD calls this, so this request must not wait for
+ * connection state changes, etc. (uses smb2_rq_internal)
+ */
+int
+smb2_smb_echo(struct smb_vc *vcp, struct smb_cred *scred, int timo)
+{
+	struct smb_rq *rqp;
+	struct mbchain *mbp;
+	int error;
+
+	error = smb_rq_alloc(VCTOCP(vcp), SMB2_ECHO, scred, &rqp);
+	if (error)
+		return (error);
+
+	/*
+	 * Build the SMB 2 Echo Request
+	 */
+	smb_rq_getrequest(rqp, &mbp);
+	mb_put_uint16le(mbp, 4);		/* Struct size */
+	mb_put_uint16le(mbp, 0);		/* Reserved */
+
+	rqp->sr_flags |= SMBR_NORECONNECT;
+	error = smb2_rq_internal(rqp, timo);
+
+	smb_rq_done(rqp);
+	return (error);
+}
