--- old/usr/src/uts/common/fs/smbsrv/smb_server.c	Mon Aug  5 17:26:39 2019
+++ new/usr/src/uts/common/fs/smbsrv/smb_server.c	Mon Aug  5 17:26:39 2019
@@ -20,8 +20,8 @@
  */
 /*
  * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
  * Copyright (c) 2017 by Delphix. All rights reserved.
+ * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
  */
 
 /*
@@ -219,13 +219,6 @@
 #include <smbsrv/smb_door.h>
 #include <smbsrv/smb_kstat.h>
 
-extern void smb_reply_notify_change_request(smb_request_t *);
-
-typedef struct {
-	smb_listener_daemon_t	*ra_listener;
-	smb_session_t		*ra_session;
-} smb_receiver_arg_t;
-
 static void smb_server_kstat_init(smb_server_t *);
 static void smb_server_kstat_fini(smb_server_t *);
 static void smb_server_timers(smb_thread_t *, void *);
@@ -236,12 +229,12 @@
 static void smb_event_cancel(smb_server_t *, uint32_t);
 static uint32_t smb_event_alloc_txid(void);
 
-static void smb_server_disconnect_share(smb_llist_t *, const char *);
-static void smb_server_enum_users(smb_llist_t *, smb_svcenum_t *);
-static void smb_server_enum_trees(smb_llist_t *, smb_svcenum_t *);
-static int smb_server_session_disconnect(smb_llist_t *, const char *,
+static void smb_server_disconnect_share(smb_server_t *, const char *);
+static void smb_server_enum_users(smb_server_t *, smb_svcenum_t *);
+static void smb_server_enum_trees(smb_server_t *, smb_svcenum_t *);
+static int smb_server_session_disconnect(smb_server_t *, const char *,
     const char *);
-static int smb_server_fclose(smb_llist_t *, uint32_t);
+static int smb_server_fclose(smb_server_t *, uint32_t);
 static int smb_server_kstat_update(kstat_t *, int);
 static int smb_server_legacy_kstat_update(kstat_t *, int);
 static void smb_server_listener_init(smb_server_t *, smb_listener_daemon_t *,
@@ -252,12 +245,25 @@
 static void smb_server_listener(smb_thread_t *, void *);
 static void smb_server_receiver(void *);
 static void smb_server_create_session(smb_listener_daemon_t *, ksocket_t);
-static void smb_server_destroy_session(smb_listener_daemon_t *,
-    smb_session_t *);
+static void smb_server_destroy_session(smb_session_t *);
 static uint16_t smb_spool_get_fid(smb_server_t *);
 static boolean_t smb_spool_lookup_doc_byfid(smb_server_t *, uint16_t,
     smb_kspooldoc_t *);
 
+/*
+ * How many "buckets" should our hash tables use?  On a "real" server,
+ * make them much larger than the number of CPUs we're likely to have.
+ * On "fksmbd" make it smaller so dtrace logs are shorter.
+ * These must be powers of two.
+ */
+#ifdef	_KERNEL
+#define	DEFAULT_HASH_NBUCKETS	256	/* real server */
+#else
+#define	DEFAULT_HASH_NBUCKETS	16	/* for "fksmbd" */
+#endif
+uint32_t SMB_OFILE_HASH_NBUCKETS = DEFAULT_HASH_NBUCKETS;
+uint32_t SMB_LEASE_HASH_NBUCKETS = DEFAULT_HASH_NBUCKETS;
+
 int smb_event_debug = 0;
 
 static smb_llist_t	smb_servers;
@@ -270,6 +276,7 @@
 kmem_cache_t		*smb_cache_odir;
 kmem_cache_t		*smb_cache_opipe;
 kmem_cache_t		*smb_cache_event;
+kmem_cache_t		*smb_cache_lock;
 
 /*
  * *****************************************************************************
@@ -308,6 +315,7 @@
 	smb_codepage_init();
 	smb_mbc_init();		/* smb_mbc_cache */
 	smb_node_init();	/* smb_node_cache, lists */
+	smb2_lease_init();
 
 	smb_cache_request = kmem_cache_create("smb_request_cache",
 	    sizeof (smb_request_t), 8, NULL, NULL, NULL, NULL, NULL, 0);
@@ -325,6 +333,8 @@
 	    sizeof (smb_opipe_t), 8, NULL, NULL, NULL, NULL, NULL, 0);
 	smb_cache_event = kmem_cache_create("smb_event_cache",
 	    sizeof (smb_event_t), 8, NULL, NULL, NULL, NULL, NULL, 0);
+	smb_cache_lock = kmem_cache_create("smb_lock_cache",
+	    sizeof (smb_lock_t), 8, NULL, NULL, NULL, NULL, NULL, 0);
 
 	smb_llist_init();
 	smb_llist_constructor(&smb_servers, sizeof (smb_server_t),
@@ -360,7 +370,9 @@
 	kmem_cache_destroy(smb_cache_odir);
 	kmem_cache_destroy(smb_cache_opipe);
 	kmem_cache_destroy(smb_cache_event);
+	kmem_cache_destroy(smb_cache_lock);
 
+	smb2_lease_fini();
 	smb_node_fini();
 	smb_mbc_fini();
 	smb_codepage_fini();
@@ -408,6 +420,15 @@
 	cv_init(&sv->sv_cv, NULL, CV_DEFAULT, NULL);
 	cv_init(&sv->sp_info.sp_cv, NULL, CV_DEFAULT, NULL);
 
+	sv->sv_persistid_ht = smb_hash_create(sizeof (smb_ofile_t),
+	    offsetof(smb_ofile_t, f_dh_lnd), SMB_OFILE_HASH_NBUCKETS);
+
+	sv->sv_lease_ht = smb_hash_create(sizeof (smb_lease_t),
+	    offsetof(smb_lease_t, ls_lnd), SMB_LEASE_HASH_NBUCKETS);
+
+	smb_llist_constructor(&sv->sv_session_list, sizeof (smb_session_t),
+	    offsetof(smb_session_t, s_lnd));
+
 	smb_llist_constructor(&sv->sv_event_list, sizeof (smb_event_t),
 	    offsetof(smb_event_t, se_lnd));
 
@@ -452,15 +473,9 @@
  * activity associated that server has ceased before destroying it.
  */
 int
-smb_server_delete(void)
+smb_server_delete(smb_server_t	*sv)
 {
-	smb_server_t	*sv;
-	int		rc;
 
-	rc = smb_server_lookup(&sv);
-	if (rc != 0)
-		return (rc);
-
 	mutex_enter(&sv->sv_mutex);
 	switch (sv->sv_state) {
 	case SMB_SERVER_STATE_RUNNING:
@@ -508,6 +523,7 @@
 	smb_kshare_fini(sv);
 	smb_kdoor_fini(sv);
 	smb_llist_destructor(&sv->sv_event_list);
+	smb_llist_destructor(&sv->sv_session_list);
 
 	kmem_free(sv->sv_disp_stats1,
 	    SMB_COM_NUM * sizeof (smb_disp_stats_t));
@@ -519,6 +535,8 @@
 	smb_thread_destroy(&sv->si_thread_timers);
 
 	mutex_destroy(&sv->sv_mutex);
+	smb_hash_destroy(sv->sv_lease_ht);
+	smb_hash_destroy(sv->sv_persistid_ht);
 	cv_destroy(&sv->sv_cv);
 	sv->sv_magic = 0;
 	kmem_free(sv, sizeof (smb_server_t));
@@ -584,6 +602,7 @@
 	int		rc = 0;
 	int		family;
 	smb_server_t	*sv;
+	cred_t		*ucr;
 
 	rc = smb_server_lookup(&sv);
 	if (rc)
@@ -596,6 +615,31 @@
 		if ((rc = smb_server_fsop_start(sv)) != 0)
 			break;
 
+		/*
+		 * Note: smb_kshare_start needs sv_session.
+		 */
+		sv->sv_session = smb_session_create(NULL, 0, sv, 0);
+		if (sv->sv_session == NULL) {
+			rc = ENOMEM;
+			break;
+		}
+
+		/*
+		 * Create a logon on the server session,
+		 * used when importing CA shares.
+		 */
+		sv->sv_rootuser = smb_user_new(sv->sv_session);
+		ucr = smb_kcred_create();
+		rc = smb_user_logon(sv->sv_rootuser, ucr, "", "root",
+		    SMB_USER_FLAG_ADMIN, 0, 0);
+		crfree(ucr);
+		ucr = NULL;
+		if (rc != 0) {
+			cmn_err(CE_NOTE, "smb_server_start: "
+			    "failed to create root user");
+			break;
+		}
+
 		if ((rc = smb_kshare_start(sv)) != 0)
 			break;
 
@@ -613,9 +657,8 @@
 		    sv->sv_cfg.skc_maxconnections, INT_MAX,
 		    curzone->zone_zsched, TASKQ_DYNAMIC);
 
-		sv->sv_session = smb_session_create(NULL, 0, sv, 0);
-
-		if (sv->sv_worker_pool == NULL || sv->sv_session == NULL) {
+		if (sv->sv_worker_pool == NULL ||
+		    sv->sv_receiver_pool == NULL) {
 			rc = ENOMEM;
 			break;
 		}
@@ -625,7 +668,7 @@
 		sv->sv_lmshrd = smb_kshare_door_init(ioc->lmshrd);
 		if (sv->sv_lmshrd == NULL)
 			break;
-		if (rc = smb_kdoor_open(sv, ioc->udoor)) {
+		if ((rc = smb_kdoor_open(sv, ioc->udoor)) != 0) {
 			cmn_err(CE_WARN, "Cannot open smbd door");
 			break;
 		}
@@ -634,7 +677,7 @@
 		fksmb_kdoor_open(sv, ioc->udoor_func);
 #endif	/* _KERNEL */
 
-		if (rc = smb_thread_start(&sv->si_thread_timers))
+		if ((rc = smb_thread_start(&sv->si_thread_timers)) != 0)
 			break;
 
 		family = AF_INET;
@@ -864,17 +907,11 @@
 
 	switch (svcenum->se_type) {
 	case SMB_SVCENUM_TYPE_USER:
-		smb_server_enum_users(&sv->sv_nbt_daemon.ld_session_list,
-		    svcenum);
-		smb_server_enum_users(&sv->sv_tcp_daemon.ld_session_list,
-		    svcenum);
+		smb_server_enum_users(sv, svcenum);
 		break;
 	case SMB_SVCENUM_TYPE_TREE:
 	case SMB_SVCENUM_TYPE_FILE:
-		smb_server_enum_trees(&sv->sv_nbt_daemon.ld_session_list,
-		    svcenum);
-		smb_server_enum_trees(&sv->sv_tcp_daemon.ld_session_list,
-		    svcenum);
+		smb_server_enum_trees(sv, svcenum);
 		break;
 	default:
 		rc = EINVAL;
@@ -890,24 +927,18 @@
 int
 smb_server_session_close(smb_ioc_session_t *ioc)
 {
-	smb_llist_t	*ll;
 	smb_server_t	*sv;
-	int		nbt_cnt;
-	int		tcp_cnt;
+	int		cnt;
 	int		rc;
 
 	if ((rc = smb_server_lookup(&sv)) != 0)
 		return (rc);
 
-	ll = &sv->sv_nbt_daemon.ld_session_list;
-	nbt_cnt = smb_server_session_disconnect(ll, ioc->client, ioc->username);
+	cnt = smb_server_session_disconnect(sv, ioc->client, ioc->username);
 
-	ll = &sv->sv_tcp_daemon.ld_session_list;
-	tcp_cnt = smb_server_session_disconnect(ll, ioc->client, ioc->username);
-
 	smb_server_release(sv);
 
-	if ((nbt_cnt == 0) && (tcp_cnt == 0))
+	if (cnt == 0)
 		return (ENOENT);
 	return (0);
 }
@@ -919,7 +950,6 @@
 smb_server_file_close(smb_ioc_fileid_t *ioc)
 {
 	uint32_t	uniqid = ioc->uniqid;
-	smb_llist_t	*ll;
 	smb_server_t	*sv;
 	int		rc;
 
@@ -926,14 +956,8 @@
 	if ((rc = smb_server_lookup(&sv)) != 0)
 		return (rc);
 
-	ll = &sv->sv_nbt_daemon.ld_session_list;
-	rc = smb_server_fclose(ll, uniqid);
+	rc = smb_server_fclose(sv, uniqid);
 
-	if (rc == ENOENT) {
-		ll = &sv->sv_tcp_daemon.ld_session_list;
-		rc = smb_server_fclose(ll, uniqid);
-	}
-
 	smb_server_release(sv);
 	return (rc);
 }
@@ -947,24 +971,22 @@
 {
 	uint32_t	counter = 0;
 
-	counter = smb_llist_get_count(&sv->sv_nbt_daemon.ld_session_list);
-	counter += smb_llist_get_count(&sv->sv_tcp_daemon.ld_session_list);
+	counter = smb_llist_get_count(&sv->sv_session_list);
 
 	return (counter);
 }
 
 /*
- * Gets the vnode of the specified share path.
- *
- * A hold on the returned vnode pointer is taken so the caller
- * must call VN_RELE.
+ * Gets the smb_node of the specified share path.
+ * Node is returned held (caller must rele.)
  */
 int
-smb_server_sharevp(smb_server_t *sv, const char *shr_path, vnode_t **vp)
+smb_server_share_lookup(smb_server_t *sv, const char *shr_path,
+    smb_node_t **nodepp)
 {
 	smb_request_t	*sr;
 	smb_node_t	*fnode = NULL;
-	smb_node_t	*dnode;
+	smb_node_t	*dnode = NULL;
 	char		last_comp[MAXNAMELEN];
 	int		rc = 0;
 
@@ -981,7 +1003,7 @@
 	mutex_exit(&sv->sv_mutex);
 
 	if ((sr = smb_request_alloc(sv->sv_session, 0)) == NULL) {
-		return (ENOMEM);
+		return (ENOTCONN);
 	}
 	sr->user_cr = zone_kcred();
 
@@ -1001,11 +1023,8 @@
 
 	ASSERT(fnode->vp && fnode->vp->v_vfsp);
 
-	VN_HOLD(fnode->vp);
-	*vp = fnode->vp;
+	*nodepp = fnode;
 
-	smb_node_release(fnode);
-
 	return (0);
 }
 
@@ -1046,7 +1065,6 @@
 smb_server_unshare(const char *sharename)
 {
 	smb_server_t	*sv;
-	smb_llist_t	*ll;
 	int		rc;
 
 	if ((rc = smb_server_lookup(&sv)))
@@ -1064,12 +1082,8 @@
 	}
 	mutex_exit(&sv->sv_mutex);
 
-	ll = &sv->sv_nbt_daemon.ld_session_list;
-	smb_server_disconnect_share(ll, sharename);
+	smb_server_disconnect_share(sv, sharename);
 
-	ll = &sv->sv_tcp_daemon.ld_session_list;
-	smb_server_disconnect_share(ll, sharename);
-
 	smb_server_release(sv);
 	return (0);
 }
@@ -1079,10 +1093,12 @@
  * Typically called when a share has been removed.
  */
 static void
-smb_server_disconnect_share(smb_llist_t *ll, const char *sharename)
+smb_server_disconnect_share(smb_server_t *sv, const char *sharename)
 {
+	smb_llist_t	*ll;
 	smb_session_t	*session;
 
+	ll = &sv->sv_session_list;
 	smb_llist_enter(ll, RW_READER);
 
 	session = smb_llist_head(ll);
@@ -1091,12 +1107,13 @@
 		smb_rwx_rwenter(&session->s_lock, RW_READER);
 		switch (session->s_state) {
 		case SMB_SESSION_STATE_NEGOTIATED:
+			smb_rwx_rwexit(&session->s_lock);
 			smb_session_disconnect_share(session, sharename);
 			break;
 		default:
+			smb_rwx_rwexit(&session->s_lock);
 			break;
 		}
-		smb_rwx_rwexit(&session->s_lock);
 		session = smb_llist_next(ll, session);
 	}
 
@@ -1241,12 +1258,13 @@
 	ASSERT(sv != NULL);
 
 	/*
-	 * This just kills old inactive sessions.  No urgency.
-	 * The session code expects one call per minute.
+	 * This kills old inactive sessions and expired durable
+	 * handles. The session code expects one call per minute.
 	 */
 	while (smb_thread_continue_timedwait(thread, 60 /* Seconds */)) {
-		smb_session_timers(&sv->sv_nbt_daemon.ld_session_list);
-		smb_session_timers(&sv->sv_tcp_daemon.ld_session_list);
+		if (sv->sv_cfg.skc_keepalive != 0)
+			smb_session_timers(sv);
+		smb2_durable_timers(sv);
 	}
 }
 
@@ -1323,6 +1341,12 @@
 }
 
 /*
+ * Verify the defines in smb_kstat.h used by ks_reqs1 ks_reqs2
+ */
+CTASSERT(SMBSRV_KS_NREQS1 == SMB_COM_NUM);
+CTASSERT(SMBSRV_KS_NREQS2 == SMB2__NCMDS);
+
+/*
  * smb_server_kstat_update
  */
 static int
@@ -1407,6 +1431,10 @@
 static void
 smb_server_shutdown(smb_server_t *sv)
 {
+	smb_llist_t *sl = &sv->sv_session_list;
+	smb_session_t *session;
+	clock_t	time;
+
 	SMB_SERVER_VALID(sv);
 
 	/*
@@ -1417,6 +1445,15 @@
 	smb_server_listener_stop(&sv->sv_tcp_daemon);
 	smb_thread_stop(&sv->si_thread_timers);
 
+	/* Disconnect all of the sessions */
+	smb_llist_enter(sl, RW_READER);
+	session = smb_llist_head(sl);
+	while (session != NULL) {
+		smb_session_disconnect(session);
+		session = smb_llist_next(sl, session);
+	}
+	smb_llist_exit(sl);
+
 	/*
 	 * Wake up any threads we might have blocked.
 	 * Must precede kdoor_close etc. because those will
@@ -1427,6 +1464,39 @@
 	smb_threshold_wake_all(&sv->sv_tcon_ct);
 	smb_threshold_wake_all(&sv->sv_opipe_ct);
 
+	/*
+	 * Wait for the session list to empty.
+	 * (cv_signal in smb_server_destroy_session)
+	 *
+	 * This should not take long, but if there are any leaked
+	 * references to ofiles, trees, or users, there could be a
+	 * session hanging around.  If that happens, the ll_count
+	 * never gets to zero and we'll never get the sv_signal.
+	 * Defend against that problem using timed wait, then
+	 * complain if we find sessions left over and continue
+	 * with shutdown in spite of any leaked sessions.
+	 * That's better than a server that won't reboot.
+	 */
+	time = SEC_TO_TICK(10) + ddi_get_lbolt();
+	mutex_enter(&sv->sv_mutex);
+	while (sv->sv_session_list.ll_count != 0) {
+		if (cv_timedwait(&sv->sv_cv, &sv->sv_mutex, time) < 0)
+			break;
+	}
+	mutex_exit(&sv->sv_mutex);
+#ifdef	DEBUG
+	if (sv->sv_session_list.ll_count != 0) {
+		cmn_err(CE_NOTE, "shutdown leaked sessions");
+		debug_enter("shutdown leaked sessions");
+	}
+#endif
+
+	/*
+	 * Clean out any durable handles.  After this we should
+	 * have no ofiles remaining (and no more oplock breaks).
+	 */
+	smb2_dh_shutdown(sv);
+
 	smb_kdoor_close(sv);
 #ifdef	_KERNEL
 	smb_kshare_door_fini(sv->sv_lmshrd);
@@ -1434,16 +1504,28 @@
 	sv->sv_lmshrd = NULL;
 
 	smb_export_stop(sv);
+	smb_kshare_stop(sv);
 
+	/*
+	 * Both kshare and the oplock break sub-systems may have
+	 * taskq jobs on the spcial "server" session, until we've
+	 * closed all ofiles and stopped the kshare exporter.
+	 * Now it's safe to destroy the server session, but first
+	 * wait for any requests on it to finish.  Note that for
+	 * normal sessions, this happens in smb_session_cancel,
+	 * but that's not called for the server session.
+	 */
+	if (sv->sv_rootuser != NULL) {
+		smb_user_logoff(sv->sv_rootuser);
+		smb_user_release(sv->sv_rootuser);
+		sv->sv_rootuser = NULL;
+	}
 	if (sv->sv_session != NULL) {
-		/*
-		 * smb_kshare_export may have a request on here.
-		 * Normal sessions do this in smb_session_cancel()
-		 * but this is a "fake" session used only for the
-		 * requests used by the kshare thread(s).
-		 */
 		smb_slist_wait_for_empty(&sv->sv_session->s_req_list);
 
+		/* Just in case import left users and trees */
+		smb_session_logoff(sv->sv_session);
+
 		smb_session_delete(sv->sv_session);
 		sv->sv_session = NULL;
 	}
@@ -1458,7 +1540,6 @@
 		sv->sv_worker_pool = NULL;
 	}
 
-	smb_kshare_stop(sv);
 	smb_server_fsop_stop(sv);
 }
 
@@ -1494,8 +1575,6 @@
 		    sizeof (ld->ld_sin6.sin6_addr.s6_addr));
 	}
 
-	smb_llist_constructor(&ld->ld_session_list, sizeof (smb_session_t),
-	    offsetof(smb_session_t, s_lnd));
 	smb_thread_init(&ld->ld_thread, name, smb_server_listener, ld,
 	    smbsrv_listen_pri);
 	ld->ld_magic = SMB_LISTENER_MAGIC;
@@ -1519,7 +1598,6 @@
 	SMB_LISTENER_VALID(ld);
 	ASSERT(ld->ld_so == NULL);
 	smb_thread_destroy(&ld->ld_thread);
-	smb_llist_destructor(&ld->ld_session_list);
 	ld->ld_magic = 0;
 }
 
@@ -1617,7 +1695,6 @@
 {
 	_NOTE(ARGUNUSED(thread))
 	smb_listener_daemon_t	*ld;
-	smb_session_t		*session;
 	ksocket_t		s_so;
 	int			on;
 	int			txbuf_size;
@@ -1628,8 +1705,24 @@
 
 	DTRACE_PROBE1(so__wait__accept, struct sonode *, ld->ld_so);
 
-	while (ksocket_accept(ld->ld_so, NULL, NULL, &s_so, CRED())
-	    == 0) {
+	for (;;) {
+		int ret = ksocket_accept(ld->ld_so, NULL, NULL, &s_so, CRED());
+
+		switch (ret) {
+		case 0:
+			break;
+		case ECONNABORTED:
+			continue;
+		case EINTR:
+		case EBADF:	/* libfakekernel */
+			goto out;
+		default:
+			cmn_err(CE_WARN,
+			    "smb_server_listener: ksocket_accept(%d)",
+			    ret);
+			goto out;
+		}
+
 		DTRACE_PROBE1(so__accept, struct sonode *, s_so);
 
 		on = 1;
@@ -1649,14 +1742,7 @@
 		 */
 		smb_server_create_session(ld, s_so);
 	}
-	/* Disconnect all the sessions this listener created. */
-	smb_llist_enter(&ld->ld_session_list, RW_READER);
-	session = smb_llist_head(&ld->ld_session_list);
-	while (session != NULL) {
-		smb_session_disconnect(session);
-		session = smb_llist_next(&ld->ld_session_list, session);
-	}
-	smb_llist_exit(&ld->ld_session_list);
+out:
 	ksocket_rele(ld->ld_so);
 }
 
@@ -1664,18 +1750,20 @@
  * smb_server_receiver
  *
  * Entry point of the receiver threads.
+ * Also does cleanup when socket disconnected.
  */
 static void
 smb_server_receiver(void *arg)
 {
-	smb_listener_daemon_t	*ld;
-	smb_session_t		*session;
+	smb_session_t	*session;
 
-	ld = ((smb_receiver_arg_t *)arg)->ra_listener;
-	session = ((smb_receiver_arg_t *)arg)->ra_session;
-	smb_mem_free(arg);
+	session = (smb_session_t *)arg;
+
+	/* We stay in here until socket disconnect. */
 	smb_session_receiver(session);
-	smb_server_destroy_session(ld, session);
+
+	ASSERT(session->s_state == SMB_SESSION_STATE_SHUTDOWN);
+	smb_server_destroy_session(session);
 }
 
 /*
@@ -1738,8 +1826,9 @@
  * Enumerate the users associated with a session list.
  */
 static void
-smb_server_enum_users(smb_llist_t *ll, smb_svcenum_t *svcenum)
+smb_server_enum_users(smb_server_t *sv, smb_svcenum_t *svcenum)
 {
+	smb_llist_t	*ll = &sv->sv_session_list;
 	smb_session_t	*sn;
 	smb_llist_t	*ulist;
 	smb_user_t	*user;
@@ -1780,8 +1869,9 @@
  * Enumerate the trees/files associated with a session list.
  */
 static void
-smb_server_enum_trees(smb_llist_t *ll, smb_svcenum_t *svcenum)
+smb_server_enum_trees(smb_server_t *sv, smb_svcenum_t *svcenum)
 {
+	smb_llist_t	*ll = &sv->sv_session_list;
 	smb_session_t	*sn;
 	smb_llist_t	*tlist;
 	smb_tree_t	*tree;
@@ -1823,54 +1913,44 @@
  * Empty strings are treated as wildcards.
  */
 static int
-smb_server_session_disconnect(smb_llist_t *ll,
+smb_server_session_disconnect(smb_server_t *sv,
     const char *client, const char *name)
 {
+	smb_llist_t	*ll = &sv->sv_session_list;
 	smb_session_t	*sn;
 	smb_llist_t	*ulist;
 	smb_user_t	*user;
-	boolean_t	match;
 	int		count = 0;
 
 	smb_llist_enter(ll, RW_READER);
-	sn = smb_llist_head(ll);
 
-	while (sn != NULL) {
+	for (sn = smb_llist_head(ll);
+	    sn != NULL;
+	    sn = smb_llist_next(ll, sn)) {
 		SMB_SESSION_VALID(sn);
 
-		if ((*client != '\0') && (!smb_session_isclient(sn, client))) {
-			sn = smb_llist_next(ll, sn);
+		if (*client != '\0' && !smb_session_isclient(sn, client))
 			continue;
-		}
 
 		ulist = &sn->s_user_list;
 		smb_llist_enter(ulist, RW_READER);
-		user = smb_llist_head(ulist);
 
-		while (user != NULL) {
-			if (smb_user_hold(user)) {
-				match = (*name == '\0');
-				if (!match)
-					match = smb_user_namecmp(user, name);
+		for (user = smb_llist_head(ulist);
+		    user != NULL;
+		    user = smb_llist_next(ulist, user)) {
+			SMB_USER_VALID(user);
 
-				if (match) {
-					smb_llist_exit(ulist);
-					smb_user_logoff(user);
-					++count;
-					smb_user_release(user);
-					smb_llist_enter(ulist, RW_READER);
-					user = smb_llist_head(ulist);
-					continue;
-				}
+			if (*name != '\0' && !smb_user_namecmp(user, name))
+				continue;
 
+			if (smb_user_hold(user)) {
+				smb_user_logoff(user);
 				smb_user_release(user);
+				count++;
 			}
-
-			user = smb_llist_next(ulist, user);
 		}
 
 		smb_llist_exit(ulist);
-		sn = smb_llist_next(ll, sn);
 	}
 
 	smb_llist_exit(ll);
@@ -1881,13 +1961,15 @@
  * Close a file by its unique id.
  */
 static int
-smb_server_fclose(smb_llist_t *ll, uint32_t uniqid)
+smb_server_fclose(smb_server_t *sv, uint32_t uniqid)
 {
+	smb_llist_t	*ll;
 	smb_session_t	*sn;
 	smb_llist_t	*tlist;
 	smb_tree_t	*tree;
 	int		rc = ENOENT;
 
+	ll = &sv->sv_session_list;
 	smb_llist_enter(ll, RW_READER);
 	sn = smb_llist_head(ll);
 
@@ -1914,6 +1996,64 @@
 	return (rc);
 }
 
+/*
+ * This is used by SMB2 session setup to find a previous session,
+ * so it can force a logoff that we haven't noticed yet.
+ * This is not called frequently, so we just walk the list of
+ * connections searching for the user.
+ */
+smb_user_t *
+smb_server_lookup_ssnid(smb_server_t *sv, uint64_t ssnid)
+{
+	smb_llist_t *sl;
+	smb_session_t *sess;
+	smb_user_t *user = NULL;
+
+	sl = &sv->sv_session_list;
+	smb_llist_enter(sl, RW_READER);
+
+	for (sess = smb_llist_head(sl);
+	    sess != NULL;
+	    sess = smb_llist_next(sl, sess)) {
+
+		SMB_SESSION_VALID(sess);
+
+		if (sess->dialect < SMB_VERS_2_BASE)
+			continue;
+
+		/*
+		 * Only look in sessions that are still active.
+		 * Avoid doing an smb_rwx_rwenter sess->s_lock
+		 * on every session here, but re-check below
+		 * with s_lock held.
+		 */
+		if (sess->s_state != SMB_SESSION_STATE_NEGOTIATED)
+			continue;
+
+		user = smb_session_lookup_ssnid(sess, ssnid);
+		if (user != NULL) {
+			break;
+		}
+	}
+
+	smb_llist_exit(sl);
+
+	/* The sess check is warning avoidance. */
+	if (user != NULL && sess != NULL) {
+		/*
+		 * Re-check the state with s_lock held.
+		 */
+		smb_rwx_rwenter(&sess->s_lock, RW_READER);
+		if (sess->s_state != SMB_SESSION_STATE_NEGOTIATED) {
+			smb_user_release(user);
+			user = NULL;
+		}
+		smb_rwx_rwexit(&sess->s_lock);
+	}
+
+	return (user);
+}
+
 /* See also: libsmb smb_kmod_setcfg */
 static void
 smb_server_store_cfg(smb_server_t *sv, smb_ioc_cfg_t *ioc)
@@ -1921,10 +2061,12 @@
 	if (ioc->maxconnections == 0)
 		ioc->maxconnections = 0xFFFFFFFF;
 
-	smb_session_correct_keep_alive_values(
-	    &sv->sv_nbt_daemon.ld_session_list, ioc->keepalive);
-	smb_session_correct_keep_alive_values(
-	    &sv->sv_tcp_daemon.ld_session_list, ioc->keepalive);
+	if (ioc->encrypt == SMB_CONFIG_REQUIRED &&
+	    ioc->max_protocol < SMB_VERS_3_0) {
+		cmn_err(CE_WARN, "Server set to require encryption; "
+		    "forcing max_protocol to 3.0");
+		ioc->max_protocol = SMB_VERS_3_0;
+	}
 
 	sv->sv_cfg.skc_maxworkers = ioc->maxworkers;
 	sv->sv_cfg.skc_maxconnections = ioc->maxconnections;
@@ -1940,6 +2082,8 @@
 	sv->sv_cfg.skc_print_enable = ioc->print_enable;
 	sv->sv_cfg.skc_traverse_mounts = ioc->traverse_mounts;
 	sv->sv_cfg.skc_max_protocol = ioc->max_protocol;
+	sv->sv_cfg.skc_min_protocol = ioc->min_protocol;
+	sv->sv_cfg.skc_encrypt = ioc->encrypt;
 	sv->sv_cfg.skc_execflags = ioc->exec_flags;
 	sv->sv_cfg.skc_negtok_len = ioc->negtok_len;
 	sv->sv_cfg.skc_version = ioc->version;
@@ -2323,10 +2467,11 @@
 smb_server_create_session(smb_listener_daemon_t *ld, ksocket_t s_so)
 {
 	smb_session_t		*session;
-	smb_receiver_arg_t	*rarg;
 	taskqid_t		tqid;
+	smb_llist_t		*sl;
+	smb_server_t		*sv = ld->ld_sv;
 
-	session = smb_session_create(s_so, ld->ld_port, ld->ld_sv,
+	session = smb_session_create(s_so, ld->ld_port, sv,
 	    ld->ld_family);
 
 	if (session == NULL) {
@@ -2336,25 +2481,20 @@
 		return;
 	}
 
-	smb_llist_enter(&ld->ld_session_list, RW_WRITER);
-	smb_llist_insert_tail(&ld->ld_session_list, session);
-	smb_llist_exit(&ld->ld_session_list);
+	sl = &sv->sv_session_list;
+	smb_llist_enter(sl, RW_WRITER);
+	smb_llist_insert_tail(sl, session);
+	smb_llist_exit(sl);
 
-	rarg = (smb_receiver_arg_t *)smb_mem_alloc(
-	    sizeof (smb_receiver_arg_t));
-	rarg->ra_listener = ld;
-	rarg->ra_session = session;
-
 	/*
 	 * These taskq entries must run independently of one another,
 	 * so TQ_NOQUEUE.  TQ_SLEEP (==0) just for clarity.
 	 */
-	tqid = taskq_dispatch(ld->ld_sv->sv_receiver_pool,
-	    smb_server_receiver, rarg, TQ_NOQUEUE | TQ_SLEEP);
+	tqid = taskq_dispatch(sv->sv_receiver_pool,
+	    smb_server_receiver, session, TQ_NOQUEUE | TQ_SLEEP);
 	if (tqid == 0) {
-		smb_mem_free(rarg);
 		smb_session_disconnect(session);
-		smb_server_destroy_session(ld, session);
+		smb_server_destroy_session(session);
 		cmn_err(CE_WARN, "SMB Session: taskq_dispatch failed");
 		return;
 	}
@@ -2363,10 +2503,41 @@
 }
 
 static void
-smb_server_destroy_session(smb_listener_daemon_t *ld, smb_session_t *session)
+smb_server_destroy_session(smb_session_t *session)
 {
-	smb_llist_enter(&ld->ld_session_list, RW_WRITER);
-	smb_llist_remove(&ld->ld_session_list, session);
-	smb_llist_exit(&ld->ld_session_list);
+	smb_server_t *sv;
+	smb_llist_t *ll;
+	uint32_t count;
+
+	ASSERT(session->s_server != NULL);
+	sv = session->s_server;
+	ll = &sv->sv_session_list;
+
+	smb_llist_flush(&session->s_tree_list);
+	smb_llist_flush(&session->s_user_list);
+
+	/*
+	 * The user and tree lists should be empty now.
+	 */
+#ifdef DEBUG
+	if (session->s_user_list.ll_count != 0) {
+		cmn_err(CE_WARN, "user list not empty?");
+		debug_enter("s_user_list");
+	}
+	if (session->s_tree_list.ll_count != 0) {
+		cmn_err(CE_WARN, "tree list not empty?");
+		debug_enter("s_tree_list");
+	}
+#endif
+
+	smb_llist_enter(ll, RW_WRITER);
+	smb_llist_remove(ll, session);
+	count = ll->ll_count;
+	smb_llist_exit(ll);
+
 	smb_session_delete(session);
+	if (count == 0) {
+		/* See smb_server_shutdown */
+		cv_signal(&sv->sv_cv);
+	}
 }
