--- old/usr/src/uts/common/fs/smbsrv/smb_session.c	Mon Aug  5 17:26:40 2019
+++ new/usr/src/uts/common/fs/smbsrv/smb_session.c	Mon Aug  5 17:26:40 2019
@@ -20,7 +20,7 @@
  */
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2019 Nexenta Systems, Inc.  All rights reserved.
  */
 
 #include <sys/atomic.h>
@@ -46,6 +46,25 @@
  */
 uint32_t smb_keep_alive = SMB_PI_KEEP_ALIVE_MIN / 60;
 
+/*
+ * There are many smbtorture test cases that send
+ * racing requests, and where the tests fail if we
+ * don't execute them in exactly the order sent.
+ * These are test bugs.  The protocol makes no
+ * guarantees about execution order of requests
+ * that are concurrently active.
+ *
+ * Nonetheless, smbtorture has many useful tests,
+ * so we have this work-around we can enable to
+ * basically force sequential execution.  When
+ * enabled, insert a delay after each request is
+ * issued a taskq job.  Enable this with mdb by
+ * setting smb_reader_delay to 10.  Don't make it
+ * more than 500 or so or the server will appear
+ * to be so slow that tests may time out.
+ */
+int smb_reader_delay = 0;  /* mSec. */
+
 static int  smbsr_newrq_initial(smb_request_t *);
 
 static void smb_session_cancel(smb_session_t *);
@@ -53,16 +72,32 @@
 static int smb_session_xprt_puthdr(smb_session_t *,
     uint8_t msg_type, uint32_t msg_len,
     uint8_t *dst, size_t dstlen);
-static smb_tree_t *smb_session_get_tree(smb_session_t *, smb_tree_t *);
-static void smb_session_logoff(smb_session_t *);
+static void smb_session_disconnect_trees(smb_session_t	*);
 static void smb_request_init_command_mbuf(smb_request_t *sr);
 static void smb_session_genkey(smb_session_t *);
+static int smb_session_kstat_update(kstat_t *, int);
+void session_stats_init(smb_server_t *, smb_session_t *);
+void session_stats_fini(smb_session_t *);
 
+/*
+ * This (legacy) code is in support of an "idle timeout" feature,
+ * which is apparently incomplete.  To complete it, we should:
+ * when the keep_alive timer expires, check whether the client
+ * has any open files, and if not then kill their session.
+ * Right now the timers are there, but nothing happens when
+ * a timer expires.
+ *
+ * Todo: complete logic to kill idle sessions.
+ *
+ * Only called when sv_cfg.skc_keepalive != 0
+ */
 void
-smb_session_timers(smb_llist_t *ll)
+smb_session_timers(smb_server_t *sv)
 {
 	smb_session_t	*session;
+	smb_llist_t	*ll;
 
+	ll = &sv->sv_session_list;
 	smb_llist_enter(ll, RW_READER);
 	session = smb_llist_head(ll);
 	while (session != NULL) {
@@ -74,44 +109,12 @@
 		if (session->keep_alive &&
 		    (session->keep_alive != (uint32_t)-1))
 			session->keep_alive--;
+
 		session = smb_llist_next(ll, session);
 	}
 	smb_llist_exit(ll);
 }
 
-void
-smb_session_correct_keep_alive_values(smb_llist_t *ll, uint32_t new_keep_alive)
-{
-	smb_session_t		*sn;
-
-	/*
-	 * Caller specifies seconds, but we track in minutes, so
-	 * convert to minutes (rounded up).
-	 */
-	new_keep_alive = (new_keep_alive + 59) / 60;
-
-	if (new_keep_alive == smb_keep_alive)
-		return;
-	/*
-	 * keep alive == 0 means do not drop connection if it's idle
-	 */
-	smb_keep_alive = (new_keep_alive) ? new_keep_alive : -1;
-
-	/*
-	 * Walk through the table and set each session to the new keep_alive
-	 * value if they have not already timed out.  Block clock interrupts.
-	 */
-	smb_llist_enter(ll, RW_READER);
-	sn = smb_llist_head(ll);
-	while (sn != NULL) {
-		SMB_SESSION_VALID(sn);
-		if (sn->keep_alive != 0)
-			sn->keep_alive = new_keep_alive;
-		sn = smb_llist_next(ll, sn);
-	}
-	smb_llist_exit(ll);
-}
-
 /*
  * Send a session message - supports SMB-over-NBT and SMB-over-TCP.
  * If an mbuf chain is provided (optional), it will be freed and
@@ -223,9 +226,9 @@
 	int			rc;
 	char			*calling_name;
 	char			*called_name;
-	char 			client_name[NETBIOS_NAME_SZ];
-	struct mbuf_chain 	mbc;
-	char 			*names = NULL;
+	char			client_name[NETBIOS_NAME_SZ];
+	struct mbuf_chain	mbc;
+	char			*names = NULL;
 	smb_wchar_t		*wbuf = NULL;
 	smb_xprt_t		hdr;
 	char *p;
@@ -417,6 +420,8 @@
 void
 smb_request_cancel(smb_request_t *sr)
 {
+	void (*cancel_method)(smb_request_t *) = NULL;
+
 	mutex_enter(&sr->sr_mutex);
 	switch (sr->sr_state) {
 
@@ -424,36 +429,31 @@
 	case SMB_REQ_STATE_SUBMITTED:
 	case SMB_REQ_STATE_ACTIVE:
 	case SMB_REQ_STATE_CLEANED_UP:
-		sr->sr_state = SMB_REQ_STATE_CANCELED;
+		sr->sr_state = SMB_REQ_STATE_CANCELLED;
 		break;
 
+	case SMB_REQ_STATE_WAITING_AUTH:
+	case SMB_REQ_STATE_WAITING_FCN1:
 	case SMB_REQ_STATE_WAITING_LOCK:
+	case SMB_REQ_STATE_WAITING_PIPE:
 		/*
-		 * This request is waiting on a lock.  Wakeup everything
-		 * waiting on the lock so that the relevant thread regains
-		 * control and notices that is has been canceled.  The
-		 * other lock request threads waiting on this lock will go
-		 * back to sleep when they discover they are still blocked.
+		 * These are states that have a cancel_method.
+		 * Make the state change now, to ensure that
+		 * we call cancel_method exactly once.  Do the
+		 * method call below, after we drop sr_mutex.
+		 * When the cancelled request thread resumes,
+		 * it should re-take sr_mutex and set sr_state
+		 * to CANCELLED, then return STATUS_CANCELLED.
 		 */
-		sr->sr_state = SMB_REQ_STATE_CANCELED;
-
-		ASSERT(sr->sr_awaiting != NULL);
-		mutex_enter(&sr->sr_awaiting->l_mutex);
-		cv_broadcast(&sr->sr_awaiting->l_cv);
-		mutex_exit(&sr->sr_awaiting->l_mutex);
+		sr->sr_state = SMB_REQ_STATE_CANCEL_PENDING;
+		cancel_method = sr->cancel_method;
+		VERIFY(cancel_method != NULL);
 		break;
 
-	case SMB_REQ_STATE_WAITING_EVENT:
-		/*
-		 * This request is waiting in change notify.
-		 */
-		sr->sr_state = SMB_REQ_STATE_CANCELED;
-		cv_signal(&sr->sr_ncr.nc_cv);
-		break;
-
-	case SMB_REQ_STATE_EVENT_OCCURRED:
+	case SMB_REQ_STATE_WAITING_FCN2:
 	case SMB_REQ_STATE_COMPLETED:
-	case SMB_REQ_STATE_CANCELED:
+	case SMB_REQ_STATE_CANCEL_PENDING:
+	case SMB_REQ_STATE_CANCELLED:
 		/*
 		 * No action required for these states since the request
 		 * is completing.
@@ -465,6 +465,10 @@
 		SMB_PANIC();
 	}
 	mutex_exit(&sr->sr_mutex);
+
+	if (cancel_method != NULL) {
+		cancel_method(sr);
+	}
 }
 
 /*
@@ -471,6 +475,11 @@
  * smb_session_receiver
  *
  * Receives request from the network and dispatches them to a worker.
+ *
+ * When we receive a disconnect here, it _could_ be due to the server
+ * having initiated disconnect, in which case the session state will be
+ * SMB_SESSION_STATE_TERMINATED and we want to keep that state so later
+ * tear-down logic will know which side initiated.
  */
 void
 smb_session_receiver(smb_session_t *session)
@@ -485,7 +494,9 @@
 		rc = smb_netbios_session_request(session);
 		if (rc != 0) {
 			smb_rwx_rwenter(&session->s_lock, RW_WRITER);
-			session->s_state = SMB_SESSION_STATE_DISCONNECTED;
+			if (session->s_state != SMB_SESSION_STATE_TERMINATED)
+				session->s_state =
+				    SMB_SESSION_STATE_DISCONNECTED;
 			smb_rwx_rwexit(&session->s_lock);
 			return;
 		}
@@ -498,7 +509,8 @@
 	(void) smb_session_reader(session);
 
 	smb_rwx_rwenter(&session->s_lock, RW_WRITER);
-	session->s_state = SMB_SESSION_STATE_DISCONNECTED;
+	if (session->s_state != SMB_SESSION_STATE_TERMINATED)
+		session->s_state = SMB_SESSION_STATE_DISCONNECTED;
 	smb_rwx_rwexit(&session->s_lock);
 
 	smb_soshutdown(session->sock);
@@ -516,7 +528,7 @@
 /*
  * smb_session_disconnect
  *
- * Disconnects the session passed in.
+ * Server-initiated disconnect (i.e. server shutdown)
  */
 void
 smb_session_disconnect(smb_session_t *session)
@@ -530,8 +542,8 @@
 	case SMB_SESSION_STATE_ESTABLISHED:
 	case SMB_SESSION_STATE_NEGOTIATED:
 		smb_soshutdown(session->sock);
-		session->s_state = SMB_SESSION_STATE_DISCONNECTED;
-		_NOTE(FALLTHRU)
+		session->s_state = SMB_SESSION_STATE_TERMINATED;
+		break;
 	case SMB_SESSION_STATE_DISCONNECTED:
 	case SMB_SESSION_STATE_TERMINATED:
 		break;
@@ -602,8 +614,11 @@
 
 		/*
 		 * Allocate a request context, read the whole message.
+		 * If the request alloc fails, we've disconnected
+		 * and won't be able to send the reply anyway, so bail now.
 		 */
-		sr = smb_request_alloc(session, hdr.xh_length);
+		if ((sr = smb_request_alloc(session, hdr.xh_length)) == NULL)
+			break;
 
 		req_buf = (uint8_t *)sr->sr_request_buf;
 		resid = hdr.xh_length;
@@ -614,8 +629,7 @@
 			break;
 		}
 
-		/* accounting: requests, received bytes */
-		smb_server_inc_req(sv);
+		/* accounting: received bytes */
 		smb_server_add_rxb(sv,
 		    (int64_t)(hdr.xh_length + NETBIOS_HDR_SZ));
 
@@ -630,6 +644,11 @@
 		sr = NULL;	/* enqueued or freed */
 		if (rc != 0)
 			break;
+
+		/* See notes where this is defined (above). */
+		if (smb_reader_delay) {
+			delay(MSEC_TO_TICK(smb_reader_delay));
+		}
 	}
 	return (rc);
 }
@@ -649,6 +668,9 @@
  * the new request for the session taskq, or smb_request_free it
  * (in case we've decided to drop this connection).  In this
  * (special) new request handler, we always free the request.
+ *
+ * Return value is 0 for success, and anything else will
+ * terminate the reader thread (drop the connection).
  */
 static int
 smbsr_newrq_initial(smb_request_t *sr)
@@ -703,6 +725,7 @@
 
 	now = ddi_get_lbolt64();
 
+	session->s_server = sv;
 	session->s_kid = SMB_NEW_KID();
 	session->s_state = SMB_SESSION_STATE_INITIALIZED;
 	session->native_os = NATIVE_OS_UNKNOWN;
@@ -709,7 +732,6 @@
 	session->opentime = now;
 	session->keep_alive = smb_keep_alive;
 	session->activity_timestamp = now;
-
 	smb_session_genkey(session);
 
 	mutex_init(&session->s_credits_mutex, NULL, MUTEX_DEFAULT, NULL);
@@ -730,7 +752,22 @@
 
 	smb_rwx_init(&session->s_lock);
 
-	if (new_so != NULL) {
+	session->s_srqueue = &sv->sv_srqueue;
+	smb_server_get_cfg(sv, &session->s_cfg);
+
+	if (new_so == NULL) {
+		/*
+		 * This call is creating the special "server" session,
+		 * used for kshare export, oplock breaks, CA import.
+		 * CA import creates temporary trees on this session
+		 * and those should never get map/unmap up-calls, so
+		 * force the map/unmap flags zero on this session.
+		 * Set a "modern" dialect for CA import too, so
+		 * pathname parse doesn't do OS/2 stuff, etc.
+		 */
+		session->s_cfg.skc_execflags = 0;
+		session->dialect = session->s_cfg.skc_max_protocol;
+	} else {
 		if (family == AF_INET) {
 			slen = sizeof (sin);
 			(void) ksocket_getsockname(new_so,
@@ -772,9 +809,6 @@
 		else
 			smb_server_inc_tcp_sess(sv);
 	}
-	session->s_server = sv;
-	smb_server_get_cfg(sv, &session->s_cfg);
-	session->s_srqueue = &sv->sv_srqueue;
 
 	/*
 	 * The initial new request handler is special,
@@ -786,16 +820,129 @@
 	session->cmd_max_bytes = SMB_REQ_MAX_SIZE;
 	session->reply_max_bytes = SMB_REQ_MAX_SIZE;
 
+	session_stats_init(sv, session);
+
 	session->s_magic = SMB_SESSION_MAGIC;
+
 	return (session);
 }
 
 void
+session_stats_init(smb_server_t *sv, smb_session_t *ss)
+{
+	static const char	*kr_names[] = SMBSRV_CLSH__NAMES;
+	char			ks_name[KSTAT_STRLEN];
+	char			*ipaddr_str = ss->ip_addr_str;
+	smbsrv_clsh_kstats_t	*ksr;
+	int			idx;
+
+	/* Don't include the special internal session (sv->sv_session). */
+	if (ss->sock == NULL)
+		return;
+
+	/*
+	 * If ipv6_enable is set to true, IPv4 addresses will be prefixed
+	 * with ::ffff: (IPv4-mapped IPv6 address), strip it.
+	 */
+	if (strncasecmp(ipaddr_str, "::ffff:", 7) == 0)
+		ipaddr_str += 7;
+
+	/*
+	 * Create raw kstats for sessions with a name composed as:
+	 * cl/$IPADDR  and instance ss->s_kid
+	 * These will look like: smbsrv:0:cl/10.10.0.5
+	 */
+	(void) snprintf(ks_name, sizeof (ks_name), "cl/%s", ipaddr_str);
+
+	ss->s_ksp = kstat_create_zone(SMBSRV_KSTAT_MODULE, ss->s_kid,
+	    ks_name, SMBSRV_KSTAT_CLASS, KSTAT_TYPE_RAW,
+	    sizeof (smbsrv_clsh_kstats_t), 0, sv->sv_zid);
+
+	if (ss->s_ksp == NULL)
+		return;
+
+	ss->s_ksp->ks_update = smb_session_kstat_update;
+	ss->s_ksp->ks_private = ss;
+
+	/*
+	 * In-line equivalent of smb_dispatch_stats_init
+	 */
+	ksr = (smbsrv_clsh_kstats_t *)ss->s_ksp->ks_data;
+	for (idx = 0; idx < SMBSRV_CLSH__NREQ; idx++) {
+		smb_latency_init(&ss->s_stats[idx].sdt_lat);
+		(void) strlcpy(ksr->ks_clsh[idx].kr_name, kr_names[idx],
+		    KSTAT_STRLEN);
+	}
+
+	kstat_install(ss->s_ksp);
+}
+
+void
+session_stats_fini(smb_session_t *ss)
+{
+	int	idx;
+
+	for (idx = 0; idx < SMBSRV_CLSH__NREQ; idx++)
+		smb_latency_destroy(&ss->s_stats[idx].sdt_lat);
+}
+
+/*
+ * Update the kstat data from our private stats.
+ */
+static int
+smb_session_kstat_update(kstat_t *ksp, int rw)
+{
+	smb_session_t *session;
+	smb_disp_stats_t *sds;
+	smbsrv_clsh_kstats_t	*clsh;
+	smb_kstat_req_t	*ksr;
+	int i;
+
+	if (rw == KSTAT_WRITE)
+		return (EACCES);
+
+	session = ksp->ks_private;
+	SMB_SESSION_VALID(session);
+	sds = session->s_stats;
+
+	clsh = (smbsrv_clsh_kstats_t *)ksp->ks_data;
+	ksr = clsh->ks_clsh;
+
+	for (i = 0; i < SMBSRV_CLSH__NREQ; i++, ksr++, sds++) {
+		ksr->kr_rxb = sds->sdt_rxb;
+		ksr->kr_txb = sds->sdt_txb;
+		mutex_enter(&sds->sdt_lat.ly_mutex);
+		ksr->kr_nreq = sds->sdt_lat.ly_a_nreq;
+		ksr->kr_sum = sds->sdt_lat.ly_a_sum;
+		ksr->kr_a_mean = sds->sdt_lat.ly_a_mean;
+		ksr->kr_a_stddev = sds->sdt_lat.ly_a_stddev;
+		ksr->kr_d_mean = sds->sdt_lat.ly_d_mean;
+		ksr->kr_d_stddev = sds->sdt_lat.ly_d_stddev;
+		sds->sdt_lat.ly_d_mean = 0;
+		sds->sdt_lat.ly_d_nreq = 0;
+		sds->sdt_lat.ly_d_stddev = 0;
+		sds->sdt_lat.ly_d_sum = 0;
+		mutex_exit(&sds->sdt_lat.ly_mutex);
+	}
+
+	return (0);
+}
+
+void
 smb_session_delete(smb_session_t *session)
 {
 
 	ASSERT(session->s_magic == SMB_SESSION_MAGIC);
 
+	if (session->s_ksp != NULL) {
+		kstat_delete(session->s_ksp);
+		session->s_ksp = NULL;
+		session_stats_fini(session);
+	}
+
+	if (session->enc_mech != NULL)
+		smb3_encrypt_fini(session);
+
 	if (session->sign_fini != NULL)
 		session->sign_fini(session);
 
@@ -847,9 +994,7 @@
 	smb_slist_wait_for_empty(&session->s_req_list);
 
 	/*
-	 * At this point the reference count of the users, trees, files,
-	 * directories should be zero. It should be possible to destroy them
-	 * without any problem.
+	 * Cleanup transact state objects
 	 */
 	xa = smb_llist_head(&session->s_xa_list);
 	while (xa) {
@@ -858,6 +1003,11 @@
 		xa = nextxa;
 	}
 
+	/*
+	 * At this point the reference count of the files and directories
+	 * should be zero. It should be possible to destroy them without
+	 * any problem, which should trigger the destruction of other objects.
+	 */
 	smb_session_logoff(session);
 }
 
@@ -895,14 +1045,24 @@
 smb_user_t *
 smb_session_lookup_uid(smb_session_t *session, uint16_t uid)
 {
-	return (smb_session_lookup_uid_st(session, uid,
+	return (smb_session_lookup_uid_st(session, 0, uid,
 	    SMB_USER_STATE_LOGGED_ON));
 }
 
+/*
+ * Find a user on the specified session by SMB2 SSNID.
+ */
 smb_user_t *
-smb_session_lookup_uid_st(smb_session_t *session, uint16_t uid,
-    smb_user_state_t st)
+smb_session_lookup_ssnid(smb_session_t *session, uint64_t ssnid)
 {
+	return (smb_session_lookup_uid_st(session, ssnid, 0,
+	    SMB_USER_STATE_LOGGED_ON));
+}
+
+smb_user_t *
+smb_session_lookup_uid_st(smb_session_t *session, uint64_t ssnid,
+    uint16_t uid, smb_user_state_t st)
+{
 	smb_user_t	*user;
 	smb_llist_t	*user_list;
 
@@ -911,17 +1071,24 @@
 	user_list = &session->s_user_list;
 	smb_llist_enter(user_list, RW_READER);
 
-	user = smb_llist_head(user_list);
-	while (user) {
+	for (user = smb_llist_head(user_list);
+	    user != NULL;
+	    user = smb_llist_next(user_list, user)) {
+
 		SMB_USER_VALID(user);
 		ASSERT(user->u_session == session);
 
-		if (user->u_uid == uid && user->u_state == st) {
-			smb_user_hold_internal(user);
+		if (user->u_ssnid != ssnid && user->u_uid != uid)
+			continue;
+
+		mutex_enter(&user->u_mutex);
+		if (user->u_state == st) {
+			// smb_user_hold_internal(user);
+			user->u_refcnt++;
+			mutex_exit(&user->u_mutex);
 			break;
 		}
-
-		user = smb_llist_next(user_list, user);
+		mutex_exit(&user->u_mutex);
 	}
 
 	smb_llist_exit(user_list);
@@ -928,17 +1095,6 @@
 	return (user);
 }
 
-void
-smb_session_post_user(smb_session_t *session, smb_user_t *user)
-{
-	SMB_USER_VALID(user);
-	ASSERT(user->u_refcnt == 0);
-	ASSERT(user->u_state == SMB_USER_STATE_LOGGED_OFF);
-	ASSERT(user->u_session == session);
-
-	smb_llist_post(&session->s_user_list, user, smb_user_delete);
-}
-
 /*
  * Find a tree by tree-id.
  */
@@ -946,7 +1102,6 @@
 smb_session_lookup_tree(
     smb_session_t	*session,
     uint16_t		tid)
-
 {
 	smb_tree_t	*tree;
 
@@ -977,91 +1132,8 @@
 }
 
 /*
- * Find the first connected tree that matches the specified sharename.
- * If the specified tree is NULL the search starts from the beginning of
- * the user's tree list.  If a tree is provided the search starts just
- * after that tree.
- */
-smb_tree_t *
-smb_session_lookup_share(
-    smb_session_t	*session,
-    const char		*sharename,
-    smb_tree_t		*tree)
-{
-	SMB_SESSION_VALID(session);
-	ASSERT(sharename);
-
-	smb_llist_enter(&session->s_tree_list, RW_READER);
-
-	if (tree) {
-		ASSERT3U(tree->t_magic, ==, SMB_TREE_MAGIC);
-		ASSERT(tree->t_session == session);
-		tree = smb_llist_next(&session->s_tree_list, tree);
-	} else {
-		tree = smb_llist_head(&session->s_tree_list);
-	}
-
-	while (tree) {
-		ASSERT3U(tree->t_magic, ==, SMB_TREE_MAGIC);
-		ASSERT(tree->t_session == session);
-		if (smb_strcasecmp(tree->t_sharename, sharename, 0) == 0) {
-			if (smb_tree_hold(tree)) {
-				smb_llist_exit(&session->s_tree_list);
-				return (tree);
-			}
-		}
-		tree = smb_llist_next(&session->s_tree_list, tree);
-	}
-
-	smb_llist_exit(&session->s_tree_list);
-	return (NULL);
-}
-
-/*
- * Find the first connected tree that matches the specified volume name.
- * If the specified tree is NULL the search starts from the beginning of
- * the user's tree list.  If a tree is provided the search starts just
- * after that tree.
- */
-smb_tree_t *
-smb_session_lookup_volume(
-    smb_session_t	*session,
-    const char		*name,
-    smb_tree_t		*tree)
-{
-	SMB_SESSION_VALID(session);
-	ASSERT(name);
-
-	smb_llist_enter(&session->s_tree_list, RW_READER);
-
-	if (tree) {
-		ASSERT3U(tree->t_magic, ==, SMB_TREE_MAGIC);
-		ASSERT(tree->t_session == session);
-		tree = smb_llist_next(&session->s_tree_list, tree);
-	} else {
-		tree = smb_llist_head(&session->s_tree_list);
-	}
-
-	while (tree) {
-		ASSERT3U(tree->t_magic, ==, SMB_TREE_MAGIC);
-		ASSERT(tree->t_session == session);
-
-		if (smb_strcasecmp(tree->t_volume, name, 0) == 0) {
-			if (smb_tree_hold(tree)) {
-				smb_llist_exit(&session->s_tree_list);
-				return (tree);
-			}
-		}
-
-		tree = smb_llist_next(&session->s_tree_list, tree);
-	}
-
-	smb_llist_exit(&session->s_tree_list);
-	return (NULL);
-}
-
-/*
  * Disconnect all trees that match the specified client process-id.
+ * Used by the SMB1 "process exit" request.
  */
 void
 smb_session_close_pid(
@@ -1068,26 +1140,27 @@
     smb_session_t	*session,
     uint32_t		pid)
 {
+	smb_llist_t	*tree_list = &session->s_tree_list;
 	smb_tree_t	*tree;
 
-	SMB_SESSION_VALID(session);
+	smb_llist_enter(tree_list, RW_READER);
 
-	tree = smb_session_get_tree(session, NULL);
+	tree = smb_llist_head(tree_list);
 	while (tree) {
-		smb_tree_t *next;
-		ASSERT3U(tree->t_magic, ==, SMB_TREE_MAGIC);
-		ASSERT(tree->t_session == session);
-		smb_tree_close_pid(tree, pid);
-		next = smb_session_get_tree(session, tree);
-		smb_tree_release(tree);
-		tree = next;
+		if (smb_tree_hold(tree)) {
+			smb_tree_close_pid(tree, pid);
+			smb_tree_release(tree);
+		}
+		tree = smb_llist_next(tree_list, tree);
 	}
+
+	smb_llist_exit(tree_list);
 }
 
 static void
-smb_session_tree_dtor(void *t)
+smb_session_tree_dtor(void *arg)
 {
-	smb_tree_t	*tree = (smb_tree_t *)t;
+	smb_tree_t	*tree = arg;
 
 	smb_tree_disconnect(tree, B_TRUE);
 	/* release the ref acquired during the traversal loop */
@@ -1118,8 +1191,9 @@
 			/*
 			 * smb_tree_hold() succeeded, hence we are in state
 			 * SMB_TREE_STATE_CONNECTED; schedule this tree
-			 * for asynchronous disconnect, which will fire
-			 * after we drop the llist traversal lock.
+			 * for disconnect after smb_llist_exit because
+			 * the "unmap exec" up-call can block, and we'd
+			 * rather not block with the tree list locked.
 			 */
 			smb_llist_post(tree_list, tree, smb_session_tree_dtor);
 		}
@@ -1133,124 +1207,114 @@
 /*
  * Disconnect all trees that this user has connected.
  */
-void
+static void
 smb_session_disconnect_trees(
     smb_session_t	*session)
 {
+	smb_llist_t	*tree_list = &session->s_tree_list;
 	smb_tree_t	*tree;
 
-	SMB_SESSION_VALID(session);
+	smb_llist_enter(tree_list, RW_READER);
 
-	tree = smb_session_get_tree(session, NULL);
+	tree = smb_llist_head(tree_list);
 	while (tree) {
-		ASSERT3U(tree->t_magic, ==, SMB_TREE_MAGIC);
-		ASSERT(tree->t_session == session);
-		smb_tree_disconnect(tree, B_TRUE);
-		smb_tree_release(tree);
-		tree = smb_session_get_tree(session, NULL);
+		if (smb_tree_hold(tree)) {
+			smb_llist_post(tree_list, tree,
+			    smb_session_tree_dtor);
+		}
+		tree = smb_llist_next(tree_list, tree);
 	}
+
+	/* drop the lock and flush the dtor queue */
+	smb_llist_exit(tree_list);
 }
 
 /*
- * Disconnect all trees that match the specified share name.
+ * Variant of smb_session_tree_dtor that also
+ * cancels requests using this tree.
  */
-void
-smb_session_disconnect_share(
-    smb_session_t	*session,
-    const char		*sharename)
+static void
+smb_session_tree_kill(void *arg)
 {
-	smb_tree_t	*tree;
-	smb_tree_t	*next;
+	smb_tree_t	*tree = arg;
 
-	SMB_SESSION_VALID(session);
-
-	tree = smb_session_lookup_share(session, sharename, NULL);
-	while (tree) {
-		ASSERT3U(tree->t_magic, ==, SMB_TREE_MAGIC);
-		ASSERT(tree->t_session == session);
-		smb_session_cancel_requests(session, tree, NULL);
-		smb_tree_disconnect(tree, B_TRUE);
-		next = smb_session_lookup_share(session, sharename, tree);
-		smb_tree_release(tree);
-		tree = next;
-	}
-}
-
-void
-smb_session_post_tree(smb_session_t *session, smb_tree_t *tree)
-{
-	SMB_SESSION_VALID(session);
 	SMB_TREE_VALID(tree);
-	ASSERT0(tree->t_refcnt);
-	ASSERT(tree->t_state == SMB_TREE_STATE_DISCONNECTED);
-	ASSERT(tree->t_session == session);
 
-	smb_llist_post(&session->s_tree_list, tree, smb_tree_dealloc);
+	smb_tree_disconnect(tree, B_TRUE);
+	smb_session_cancel_requests(tree->t_session, tree, NULL);
+
+	/* release the ref acquired during the traversal loop */
+	smb_tree_release(tree);
 }
 
 /*
- * Get the next connected tree in the list.  A reference is taken on
- * the tree, which can be released later with smb_tree_release().
- *
- * If the specified tree is NULL the search starts from the beginning of
- * the tree list.  If a tree is provided the search starts just after
- * that tree.
- *
- * Returns NULL if there are no connected trees in the list.
+ * Disconnect all trees that match the specified share name,
+ * and kill requests using those trees.
  */
-static smb_tree_t *
-smb_session_get_tree(
+void
+smb_session_disconnect_share(
     smb_session_t	*session,
-    smb_tree_t		*tree)
+    const char		*sharename)
 {
-	smb_llist_t	*tree_list;
+	smb_llist_t	*ll;
+	smb_tree_t	*tree;
 
 	SMB_SESSION_VALID(session);
-	tree_list = &session->s_tree_list;
 
-	smb_llist_enter(tree_list, RW_READER);
+	ll = &session->s_tree_list;
+	smb_llist_enter(ll, RW_READER);
 
-	if (tree) {
-		ASSERT3U(tree->t_magic, ==, SMB_TREE_MAGIC);
-		tree = smb_llist_next(tree_list, tree);
-	} else {
-		tree = smb_llist_head(tree_list);
-	}
+	for (tree = smb_llist_head(ll);
+	    tree != NULL;
+	    tree = smb_llist_next(ll, tree)) {
 
-	while (tree) {
-		if (smb_tree_hold(tree))
-			break;
+		SMB_TREE_VALID(tree);
+		ASSERT(tree->t_session == session);
 
-		tree = smb_llist_next(tree_list, tree);
+		if (smb_strcasecmp(tree->t_sharename, sharename, 0) != 0)
+			continue;
+
+		if (smb_tree_hold(tree)) {
+			smb_llist_post(ll, tree,
+			    smb_session_tree_kill);
+		}
 	}
 
-	smb_llist_exit(tree_list);
-	return (tree);
+	smb_llist_exit(ll);
 }
 
 /*
  * Logoff all users associated with the specified session.
+ *
+ * This is called for both server-initiated disconnect
+ * (SMB_SESSION_STATE_TERMINATED) and client-initiated
+ * disconnect (SMB_SESSION_STATE_DISCONNECTED).
+ * If client-initiated, save durable handles.
  */
-static void
+void
 smb_session_logoff(smb_session_t *session)
 {
+	smb_llist_t	*ulist;
 	smb_user_t	*user;
 
 	SMB_SESSION_VALID(session);
 
-	smb_session_disconnect_trees(session);
+top:
+	ulist = &session->s_user_list;
+	smb_llist_enter(ulist, RW_READER);
 
-	smb_llist_enter(&session->s_user_list, RW_READER);
-
-	user = smb_llist_head(&session->s_user_list);
+	user = smb_llist_head(ulist);
 	while (user) {
 		SMB_USER_VALID(user);
 		ASSERT(user->u_session == session);
 
+		mutex_enter(&user->u_mutex);
 		switch (user->u_state) {
 		case SMB_USER_STATE_LOGGING_ON:
 		case SMB_USER_STATE_LOGGED_ON:
-			smb_user_hold_internal(user);
+			// smb_user_hold_internal(user);
+			user->u_refcnt++;
+			mutex_exit(&user->u_mutex);
 			smb_user_logoff(user);
 			smb_user_release(user);
 			break;
@@ -1257,17 +1321,71 @@
 
 		case SMB_USER_STATE_LOGGED_OFF:
 		case SMB_USER_STATE_LOGGING_OFF:
+			mutex_exit(&user->u_mutex);
 			break;
 
 		default:
+			mutex_exit(&user->u_mutex);
 			ASSERT(0);
 			break;
 		}
 
-		user = smb_llist_next(&session->s_user_list, user);
+		user = smb_llist_next(ulist, user);
 	}
 
-	smb_llist_exit(&session->s_user_list);
+	/* Needed below (Was the list empty?) */
+	user = smb_llist_head(ulist);
+
+	smb_llist_exit(ulist);
+
+	/*
+	 * It's possible for user objects to remain due to references
+	 * obtained via smb_server_lookup_ssnid(), when an SMB2
+	 * session setup is destroying a previous session.
+	 *
+	 * Wait for user objects to clear out (last refs. go away,
+	 * then smb_user_delete takes them out of the list).  When
+	 * the last user object is removed, the session state is
+	 * set to SHUTDOWN and s_lock is signaled.
+	 *
+	 * Not all places that call smb_user_release necessarily
+	 * flush the delete queue, so after we wait for the list
+	 * to empty out, go back to the top and recheck the list
+	 * delete queue to make sure smb_user_delete happens.
+	 */
+	if (user == NULL) {
+		/* User list is empty. */
+		smb_rwx_rwenter(&session->s_lock, RW_WRITER);
+		session->s_state = SMB_SESSION_STATE_SHUTDOWN;
+		smb_rwx_rwexit(&session->s_lock);
+	} else {
+		smb_rwx_rwenter(&session->s_lock, RW_READER);
+		if (session->s_state != SMB_SESSION_STATE_SHUTDOWN) {
+			(void) smb_rwx_cvwait(&session->s_lock,
+			    MSEC_TO_TICK(200));
+			smb_rwx_rwexit(&session->s_lock);
+			goto top;
+		}
+		smb_rwx_rwexit(&session->s_lock);
+	}
+	ASSERT(session->s_state == SMB_SESSION_STATE_SHUTDOWN);
+
+	/*
+	 * User list should be empty now.
+	 */
+#ifdef	DEBUG
+	if (ulist->ll_count != 0) {
+		cmn_err(CE_WARN, "user list not empty?");
+		debug_enter("s_user_list");
+	}
+#endif
+
+	/*
+	 * User logoff happens first so we'll set preserve_opens
+	 * for client-initiated disconnect.  When that's done
+	 * there should be no trees left, but check anyway.
+	 */
+	smb_session_disconnect_trees(session);
 }
 
 /*
@@ -1320,7 +1438,8 @@
  * Allocate an smb_request_t structure from the kmem_cache.  Partially
  * initialize the found/new request.
  *
- * Returns pointer to a request
+ * Returns pointer to a request, or NULL if the session state is
+ * one in which new requests are no longer allowed.
  */
 smb_request_t *
 smb_request_alloc(smb_session_t *session, int req_length)
@@ -1340,7 +1459,6 @@
 	bzero(sr, sizeof (smb_request_t));
 
 	mutex_init(&sr->sr_mutex, NULL, MUTEX_DEFAULT, NULL);
-	cv_init(&sr->sr_ncr.nc_cv, NULL, CV_DEFAULT, NULL);
 	smb_srm_init(sr);
 	sr->session = session;
 	sr->sr_server = session->s_server;
@@ -1353,7 +1471,37 @@
 		sr->sr_request_buf = kmem_alloc(req_length, KM_SLEEP);
 	sr->sr_magic = SMB_REQ_MAGIC;
 	sr->sr_state = SMB_REQ_STATE_INITIALIZING;
-	smb_slist_insert_tail(&session->s_req_list, sr);
+
+	/*
+	 * Only allow new SMB requests in some states.
+	 */
+	smb_rwx_rwenter(&session->s_lock, RW_WRITER);
+	switch (session->s_state) {
+	case SMB_SESSION_STATE_CONNECTED:
+	case SMB_SESSION_STATE_INITIALIZED:
+	case SMB_SESSION_STATE_ESTABLISHED:
+	case SMB_SESSION_STATE_NEGOTIATED:
+		smb_slist_insert_tail(&session->s_req_list, sr);
+		break;
+
+	default:
+		ASSERT(0);
+		/* FALLTHROUGH */
+	case SMB_SESSION_STATE_DISCONNECTED:
+	case SMB_SESSION_STATE_SHUTDOWN:
+	case SMB_SESSION_STATE_TERMINATED:
+		/* Disallow new requests in these states. */
+		if (sr->sr_request_buf)
+			kmem_free(sr->sr_request_buf, sr->sr_req_length);
+		sr->session = NULL;
+		sr->sr_magic = 0;
+		mutex_destroy(&sr->sr_mutex);
+		kmem_cache_free(smb_cache_request, sr);
+		sr = NULL;
+		break;
+	}
+	smb_rwx_rwexit(&session->s_lock);
+
 	return (sr);
 }
 
@@ -1368,10 +1516,8 @@
 	ASSERT(sr->sr_magic == SMB_REQ_MAGIC);
 	ASSERT(sr->session);
 	ASSERT(sr->r_xa == NULL);
-	ASSERT(sr->sr_ncr.nc_fname == NULL);
 
 	if (sr->fid_ofile != NULL) {
-		smb_ofile_request_complete(sr->fid_ofile);
 		smb_ofile_release(sr->fid_ofile);
 	}
 
@@ -1381,6 +1527,15 @@
 	if (sr->uid_user != NULL)
 		smb_user_release(sr->uid_user);
 
+	if (sr->tform_ssn != NULL)
+		smb_user_release(sr->tform_ssn);
+
+	/*
+	 * The above may have left work on the delete queues
+	 */
+	smb_llist_flush(&sr->session->s_tree_list);
+	smb_llist_flush(&sr->session->s_user_list);
+
 	smb_slist_remove(&sr->session->s_req_list, sr);
 
 	sr->session = NULL;
@@ -1397,7 +1552,6 @@
 		m_freem(sr->raw_data.chain);
 
 	sr->sr_magic = 0;
-	cv_destroy(&sr->sr_ncr.nc_cv);
 	mutex_destroy(&sr->sr_mutex);
 	kmem_cache_free(smb_cache_request, sr);
 }
@@ -1417,10 +1571,6 @@
 {
 	SMB_SESSION_VALID(session);
 
-	/* Clients using SMB2 and later always know about oplocks. */
-	if (session->dialect > NT_LM_0_12)
-		return (B_TRUE);
-
 	/* Older clients only do Level II oplocks if negotiated. */
 	if ((session->capabilities & CAP_LEVEL_II_OPLOCKS) != 0)
 		return (B_TRUE);
@@ -1428,34 +1578,6 @@
 	return (B_FALSE);
 }
 
-/*
- * smb_session_oplock_break
- *
- * Send an oplock break request to the client,
- * recalling some cache delegation.
- */
-void
-smb_session_oplock_break(smb_request_t *sr, uint8_t brk)
-{
-	smb_session_t	*session = sr->session;
-	mbuf_chain_t	*mbc = &sr->reply;
-
-	SMB_SESSION_VALID(session);
-
-	/*
-	 * Build the break message in sr->reply and then send it.
-	 * The mbc is free'd later, in smb_request_free().
-	 */
-	mbc->max_bytes = MLEN;
-	if (session->dialect <= NT_LM_0_12) {
-		smb1_oplock_break_notification(sr, brk);
-	} else {
-		smb2_oplock_break_notification(sr, brk);
-	}
-
-	(void) smb_session_send(session, 0, mbc);
-}
-
 static void
 smb_session_genkey(smb_session_t *session)
 {
