static smb_tree_t *
smb_tree_connect_disk(smb_request_t *sr, const char *sharename)
{
	smb_user_t		*user = sr->uid_user;
	smb_node_t		*dnode = NULL;
	smb_node_t		*snode = NULL;
	char			last_component[MAXNAMELEN];
	smb_tree_t		*tree;
	smb_share_t 		*si;
	cred_t			*u_cred;
	int			rc;
	uint32_t		access = 0; /* read/write is assumed */
	uint32_t		hostaccess = ACE_ALL_PERMS;
	uint32_t		aclaccess;
	smb_execsub_info_t	subs;

	ASSERT(user);
	u_cred = user->u_cred;
	ASSERT(u_cred);

	if (user->u_flags & SMB_USER_FLAG_IPC) {
		smb_tree_log(sr, sharename, "access denied: IPC only");
		smbsr_error(sr, NT_STATUS_ACCESS_DENIED, ERRSRV, ERRaccess);
		return (NULL);
	}

	si = kmem_zalloc(sizeof (smb_share_t), KM_SLEEP);

	if (smb_kshare_getinfo(sr->sr_server->sv_lmshrd, (char *)sharename, si,
	    &sr->session->ipaddr) != NERR_Success) {
		smb_tree_log(sr, sharename, "share not found");
		smbsr_error(sr, 0, ERRSRV, ERRinvnetname);
		kmem_free(si, sizeof (smb_share_t));
		return (NULL);
	}

	if (user->u_flags & SMB_USER_FLAG_GUEST) {
		if ((si->shr_flags & SMB_SHRF_GUEST_OK) == 0) {
			smb_tree_log(sr, sharename,
			    "access denied: guest disabled");
			smbsr_error(sr, NT_STATUS_ACCESS_DENIED, ERRSRV,
			    ERRaccess);
			kmem_free(si, sizeof (smb_share_t));
			return (NULL);
		}
	}

	/*
	 * Handle the default administration shares: C$, D$ etc.
	 * Only a user with admin rights is allowed to map these
	 * shares.
	 */
	if (si->shr_flags & SMB_SHRF_ADMIN) {
		if (!smb_user_is_admin(user)) {
			smb_tree_log(sr, sharename, "access denied: not admin");
			smbsr_error(sr, NT_STATUS_ACCESS_DENIED,
			    ERRSRV, ERRaccess);
			kmem_free(si, sizeof (smb_share_t));
			return (NULL);
		}
	}

	/*
	 * Set up the OptionalSupport for this share.
	 */
	sr->arg.tcon.optional_support = SMB_SUPPORT_SEARCH_BITS;

	switch (si->shr_flags & SMB_SHRF_CSC_MASK) {
	case SMB_SHRF_CSC_DISABLED:
		sr->arg.tcon.optional_support |= SMB_CSC_CACHE_NONE;
		break;
	case SMB_SHRF_CSC_AUTO:
		sr->arg.tcon.optional_support |= SMB_CSC_CACHE_AUTO_REINT;
		break;
	case SMB_SHRF_CSC_VDO:
		sr->arg.tcon.optional_support |= SMB_CSC_CACHE_VDO;
		break;
	case SMB_SHRF_CSC_MANUAL:
	default:
		/*
		 * Default to SMB_CSC_CACHE_MANUAL_REINT.
		 */
		break;
	}

	/* ABE support */
	if (si->shr_flags & SMB_SHRF_ABE)
		sr->arg.tcon.optional_support |=
		    SHI1005_FLAGS_ACCESS_BASED_DIRECTORY_ENUM;

	access = si->shr_access_value & SMB_SHRF_ACC_ALL;

	if (access == SMB_SHRF_ACC_RO) {
		hostaccess &= ~ACE_ALL_WRITE_PERMS;
	} else if (access == SMB_SHRF_ACC_NONE) {
		kmem_free(si, sizeof (smb_share_t));
		smb_tree_log(sr, sharename, "access denied: host access");
		smbsr_error(sr, NT_STATUS_ACCESS_DENIED, ERRSRV, ERRaccess);
		return (NULL);
	}

	/*
	 * Check that the shared directory exists.
	 */
	rc = smb_pathname_reduce(sr, u_cred, si->shr_path, 0, 0, &dnode,
	    last_component);

	if (rc == 0) {
		rc = smb_fsop_lookup(sr, u_cred, SMB_FOLLOW_LINKS,
		    sr->sr_server->si_root_smb_node, dnode, last_component,
		    &snode);

		smb_node_release(dnode);
	}

	if (rc) {
		if (snode)
			smb_node_release(snode);

		smb_tree_log(sr, sharename, "bad path: %s", si->shr_path);
		smbsr_error(sr, 0, ERRSRV, ERRinvnetname);
		kmem_free(si, sizeof (smb_share_t));
		return (NULL);
	}

	/*
	 * Find share level ACL if it exists in the designated
	 * location. Needs to be done after finding a valid path but
	 * before the tree is allocated.
	 */
	smb_tree_acl_access(u_cred, sharename, snode->vp, &aclaccess);
	if ((aclaccess & ACE_ALL_PERMS) == 0) {
		smb_tree_log(sr, sharename, "access denied: share ACL");
		smbsr_error(sr, 0, ERRSRV, ERRaccess);
		kmem_free(si, sizeof (smb_share_t));
		smb_node_release(snode);
		return (NULL);
	}

	/*
	 * Set tree ACL access to the minimum ACL permissions based on
	 * hostaccess (those allowed by host based access) and
	 * aclaccess (those from the ACL object for the share). This
	 * is done during the alloc.
	 */

	(void) strlcpy(si->shr_name, sharename, MAXNAMELEN);
	tree = smb_tree_alloc(user, si, STYPE_DISKTREE, snode,
	    hostaccess & aclaccess);

	smb_node_release(snode);

	if (tree == NULL)
		smbsr_error(sr, NT_STATUS_ACCESS_DENIED, ERRSRV, ERRaccess);
	else {

		tree->t_shr_flags = si->shr_flags;

		if (tree->t_shr_flags & SMB_SHRF_MAP) {
			(void) smb_tree_set_execsub_info(tree, &subs);

			rc = smb_kshare_exec(sr->sr_server->sv_lmshrd,
			    (char *)sharename, &subs, SMB_SHR_MAP);

			if (rc != 0 && tree->t_shr_flags & SMB_SHRF_DISP_TERM) {
				smb_tree_disconnect(tree, B_FALSE);
				smb_tree_release(tree);
				smbsr_error(sr, NT_STATUS_ACCESS_DENIED, ERRSRV,
				    ERRaccess);
				kmem_free(si, sizeof (smb_share_t));
				return (NULL);
			}
		}
	}

	kmem_free(si, sizeof (smb_share_t));

	return (tree);
}
/*
 * This is the common dispatch function for SMB2, used for both
 * synchronous and asynchronous requests.  In the async case,
 * this runs twice: once for the initial processing where the
 * initial handler returns NT_STATUS_PENDING, and then a second
 * time (with async_func != NULL) for the "real work".
 * Note the async_func == NULL for "normal" calls, and the
 * handler function is taken from the dispatch table.
 */
static int
smb2sr_dispatch(smb_request_t *sr,
	smb_sdrc_t	(*async_func)(smb_request_t *))
{
	const smb_disp_entry_t	*sdd;
	smb_disp_stats_t	*sds;
	smb_session_t		*session;
	smb_server_t		*server;
	boolean_t		related;
	int			rc = 0;

	session = sr->session;
	server = session->s_server;

	/*
	 * Validate the commmand code, get dispatch table entries.
	 * [MS-SMB2] 3.3.5.2.6 Handling Incorrectly Formatted...
	 *
	 * The last slot in the dispatch table is used to handle
	 * invalid commands.  Same for statistics.
	 */
	if (sr->smb2_cmd_code < SMB2_INVALID_CMD) {
		sdd = &smb2_disp_table[sr->smb2_cmd_code];
		sds = &server->sv_disp_stats2[sr->smb2_cmd_code];
	} else {
		sdd = &smb2_disp_table[SMB2_INVALID_CMD];
		sds = &server->sv_disp_stats2[SMB2_INVALID_CMD];
	}

	if (sr->smb2_hdr_flags & SMB2_FLAGS_SERVER_TO_REDIR) {
		smb2sr_put_error(sr, NT_STATUS_INVALID_PARAMETER);
		goto done;
	}

	/*
	 * If this command is NOT "related" to the previous,
	 * clear out the UID, TID, FID state that might be
	 * left over from the previous command.
	 *
	 * Also, if the command IS related, but is declining to
	 * inherit the previous UID or TID, then clear out the
	 * previous session or tree now.  This simplifies the
	 * inheritance logic below.  Similar logic for FIDs
	 * happens in smb2sr_lookup_fid()
	 */
	related = (sr->smb2_hdr_flags & SMB2_FLAGS_RELATED_OPERATIONS);
	if (!related &&
	    sr->fid_ofile != NULL) {
		smb_ofile_request_complete(sr->fid_ofile);
		smb_ofile_release(sr->fid_ofile);
		sr->fid_ofile = NULL;
	}
	if ((!related || sr->smb_tid != INHERIT_ID) &&
	    sr->tid_tree != NULL) {
		smb_tree_release(sr->tid_tree);
		sr->tid_tree = NULL;
	}
	if ((!related || sr->smb_uid != INHERIT_ID) &&
	    sr->uid_user != NULL) {
		smb_user_release(sr->uid_user);
		sr->uid_user = NULL;
	}

	/*
	 * Make sure we have a user and tree as needed
	 * according to the flags for the this command.
	 * In a compound, a "related" command may inherit
	 * the UID, TID, and FID from previous commands
	 * using the special INHERIT_ID (all ones).
	 */

	if ((sdd->sdt_flags & SDDF_SUPPRESS_UID) == 0) {
		/*
		 * This command requires a user session.
		 */
		if (related && sr->smb_uid == INHERIT_ID &&
		    sr->uid_user != NULL) {
			sr->smb_uid = sr->uid_user->u_uid;
		} else {
			ASSERT3P(sr->uid_user, ==, NULL);
			sr->uid_user = smb_session_lookup_uid(session,
			    sr->smb_uid);
		}
		if (sr->uid_user == NULL) {
			/* [MS-SMB2] 3.3.5.2.9 Verifying the Session */
			smb2sr_put_error(sr, NT_STATUS_USER_SESSION_DELETED);
			goto done;
		}
		sr->user_cr = smb_user_getcred(sr->uid_user);
	}

	if ((sdd->sdt_flags & SDDF_SUPPRESS_TID) == 0) {
		/*
		 * This command requires a tree connection.
		 */
		if (related && sr->smb_tid == INHERIT_ID &&
		    sr->tid_tree != NULL) {
			sr->smb_tid = sr->tid_tree->t_tid;
		} else {
			ASSERT3P(sr->tid_tree, ==, NULL);
			sr->tid_tree = smb_session_lookup_tree(session,
			    sr->smb_tid);
		}
		if (sr->tid_tree == NULL) {
			/* [MS-SMB2] 3.3.5.2.11 Verifying the Tree Connect */
			smb2sr_put_error(sr, NT_STATUS_NETWORK_NAME_DELETED);
			goto done;
		}
	}

	/*
	 * The real work: call the SMB2 command handler.
	 */
	sr->sr_time_start = gethrtime();
	if (async_func != NULL) {
		rc = (*async_func)(sr);
	} else {
		/* NB: not using pre_op */
		rc = (*sdd->sdt_function)(sr);
		/* NB: not using post_op */
	}

	MBC_FLUSH(&sr->raw_data);

done:
	/*
	 * Pad the reply to align(8) if necessary.
	 */
	if (sr->reply.chain_offset & 7) {
		int padsz = 8 - (sr->reply.chain_offset & 7);
		(void) smb_mbc_encodef(&sr->reply, "#.", padsz);
	}
	ASSERT((sr->reply.chain_offset & 7) == 0);

	/*
	 * Record some statistics: latency, rx bytes, tx bytes
	 */
	smb_latency_add_sample(&sds->sdt_lat,
	    gethrtime() - sr->sr_time_start);
	atomic_add_64(&sds->sdt_rxb,
	    (int64_t)(sr->command.chain_offset - sr->smb2_cmd_hdr));
	atomic_add_64(&sds->sdt_txb,
	    (int64_t)(sr->reply.chain_offset - sr->smb2_reply_hdr));

	return (rc);
}