--- old/usr/src/uts/common/fs/smbsrv/smb_user.c	Mon Aug  5 17:26:42 2019
+++ new/usr/src/uts/common/fs/smbsrv/smb_user.c	Mon Aug  5 17:26:42 2019
@@ -20,7 +20,7 @@
  */
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2014 Nexenta Systems, Inc. All rights reserved.
+ * Copyright 2018 Nexenta Systems, Inc. All rights reserved.
  * Copyright (c) 2016 by Delphix. All rights reserved.
  */
 
@@ -181,7 +181,9 @@
  *    Rules of access to a user structure:
  *
  *    1) In order to avoid deadlocks, when both (mutex and lock of the session
- *       list) have to be entered, the lock must be entered first.
+ *       list) have to be entered, the lock must be entered first. Additionally,
+ *       one may NOT flush the deleteq of either the tree list or the ofile list
+ *       while the user mutex is held.
  *
  *    2) All actions applied to a user require a reference count.
  *
@@ -208,22 +210,36 @@
 
 #define	ADMINISTRATORS_SID	"S-1-5-32-544"
 
+/* Don't leak object addresses */
+#define	SMB_USER_SSNID(u) \
+	((uintptr_t)&smb_cache_user ^ (uintptr_t)(u))
+
+static void smb_user_delete(void *);
 static int smb_user_enum_private(smb_user_t *, smb_svcenum_t *);
 static void smb_user_auth_logoff(smb_user_t *);
+static void smb_user_logoff_tq(void *);
 
-
 /*
  * Create a new user.
+ *
+ * For SMB2 and later, session IDs (u_ssnid) need to be unique among all
+ * current and "recent" sessions.  The session ID is derived from the
+ * address of the smb_user object (obscured by XOR with a constant).
+ * This adds a 3-bit generation number in the low bits, incremented
+ * when we allocate an smb_user_t from its kmem cache, so it can't
+ * be confused with a (recent) previous incarnation of this object.
  */
 smb_user_t *
 smb_user_new(smb_session_t *session)
 {
 	smb_user_t	*user;
+	uint_t		gen;	// generation (low 3 bits of ssnid)
 
 	ASSERT(session);
 	ASSERT(session->s_magic == SMB_SESSION_MAGIC);
 
 	user = kmem_cache_alloc(smb_cache_user, KM_SLEEP);
+	gen = (user->u_ssnid + 1) & 7;
 	bzero(user, sizeof (smb_user_t));
 
 	user->u_refcnt = 1;
@@ -233,6 +249,7 @@
 
 	if (smb_idpool_alloc(&session->s_uid_pool, &user->u_uid))
 		goto errout;
+	user->u_ssnid = SMB_USER_SSNID(user) + gen;
 
 	mutex_init(&user->u_mutex, NULL, MUTEX_DEFAULT, NULL);
 	user->u_state = SMB_USER_STATE_LOGGING_ON;
@@ -266,6 +283,7 @@
     uint32_t		privileges,
     uint32_t		audit_sid)
 {
+	ksocket_t authsock = NULL;
 
 	ASSERT(user->u_magic == SMB_USER_MAGIC);
 	ASSERT(cr);
@@ -279,7 +297,16 @@
 		return (-1);
 	}
 
-	smb_authsock_close(user);
+	/*
+	 * In the transition from LOGGING_ON to LOGGED_ON,
+	 * we always have an auth. socket to close.
+	 */
+	authsock = user->u_authsock;
+	user->u_authsock = NULL;
+	if (user->u_auth_tmo != NULL) {
+		(void) untimeout(user->u_auth_tmo);
+		user->u_auth_tmo = NULL;
+	}
 
 	user->u_state = SMB_USER_STATE_LOGGED_ON;
 	user->u_flags = flags;
@@ -293,6 +320,10 @@
 
 	mutex_exit(&user->u_mutex);
 
+	/* This close can block, so not under the mutex. */
+	if (authsock != NULL)
+		smb_authsock_close(user, authsock);
+
 	return (0);
 }
 
@@ -299,26 +330,39 @@
 /*
  * smb_user_logoff
  *
- * Change the user state and disconnect trees.
+ * Change the user state to "logging off" and disconnect trees.
  * The user list must not be entered or modified here.
+ *
+ * We remain in state "logging off" until the last ref. is gone,
+ * then smb_user_release takes us to state "logged off".
  */
 void
 smb_user_logoff(
     smb_user_t		*user)
 {
+	ksocket_t authsock = NULL;
+
 	ASSERT(user->u_magic == SMB_USER_MAGIC);
 
 	mutex_enter(&user->u_mutex);
 	ASSERT(user->u_refcnt);
 	switch (user->u_state) {
-	case SMB_USER_STATE_LOGGING_ON: {
-		smb_authsock_close(user);
-		user->u_state = SMB_USER_STATE_LOGGED_OFF;
-		smb_server_dec_users(user->u_server);
+	case SMB_USER_STATE_LOGGING_ON:
+		authsock = user->u_authsock;
+		user->u_authsock = NULL;
+		if (user->u_auth_tmo != NULL) {
+			(void) untimeout(user->u_auth_tmo);
+			user->u_auth_tmo = NULL;
+		}
+		user->u_state = SMB_USER_STATE_LOGGING_OFF;
+		mutex_exit(&user->u_mutex);
+		/* This close can block, so not under the mutex. */
+		if (authsock != NULL) {
+			smb_authsock_close(user, authsock);
+		}
 		break;
-	}
 
-	case SMB_USER_STATE_LOGGED_ON: {
+	case SMB_USER_STATE_LOGGED_ON:
 		/*
 		 * The user is moved into a state indicating that the log off
 		 * process has started.
@@ -327,20 +371,18 @@
 		mutex_exit(&user->u_mutex);
 		smb_session_disconnect_owned_trees(user->u_session, user);
 		smb_user_auth_logoff(user);
-		mutex_enter(&user->u_mutex);
-		user->u_state = SMB_USER_STATE_LOGGED_OFF;
-		smb_server_dec_users(user->u_server);
 		break;
-	}
+
 	case SMB_USER_STATE_LOGGED_OFF:
 	case SMB_USER_STATE_LOGGING_OFF:
+		mutex_exit(&user->u_mutex);
 		break;
 
 	default:
 		ASSERT(0);
+		mutex_exit(&user->u_mutex);
 		break;
 	}
-	mutex_exit(&user->u_mutex);
 }
 
 /*
@@ -387,23 +429,32 @@
 smb_user_release(
     smb_user_t		*user)
 {
-	ASSERT(user->u_magic == SMB_USER_MAGIC);
+	smb_session_t *ssn = user->u_session;
 
+	SMB_USER_VALID(user);
+
+	/* flush the tree list delete queue */
+	smb_llist_flush(&ssn->s_tree_list);
+
 	mutex_enter(&user->u_mutex);
 	ASSERT(user->u_refcnt);
 	user->u_refcnt--;
 
 	switch (user->u_state) {
-	case SMB_USER_STATE_LOGGED_OFF:
-		if (user->u_refcnt == 0)
-			smb_session_post_user(user->u_session, user);
+	case SMB_USER_STATE_LOGGING_OFF:
+		if (user->u_refcnt == 0) {
+			smb_session_t *ssn = user->u_session;
+			user->u_state = SMB_USER_STATE_LOGGED_OFF;
+			smb_llist_post(&ssn->s_user_list, user,
+			    smb_user_delete);
+		}
 		break;
 
 	case SMB_USER_STATE_LOGGING_ON:
 	case SMB_USER_STATE_LOGGED_ON:
-	case SMB_USER_STATE_LOGGING_OFF:
 		break;
 
+	case SMB_USER_STATE_LOGGED_OFF:
 	default:
 		ASSERT(0);
 		break;
@@ -412,6 +463,77 @@
 }
 
 /*
+ * Timeout handler for user logons that stay too long in
+ * state SMB_USER_STATE_LOGGING_ON.  This is setup by a
+ * timeout call in smb_authsock_open, and called in a
+ * callout thread, so schedule a taskq job to do the
+ * real work of logging off this user.
+ */
+void
+smb_user_auth_tmo(void *arg)
+{
+	smb_user_t *user = arg;
+	smb_request_t *sr;
+
+	SMB_USER_VALID(user);
+
+	/*
+	 * If we can't allocate a request, it means the
+	 * session is being torn down, so nothing to do.
+	 */
+	sr = smb_request_alloc(user->u_session, 0);
+	if (sr == NULL)
+		return;
+
+	/*
+	 * Check user state, and take a hold if it's
+	 * still logging on.  If not, we're done.
+	 */
+	mutex_enter(&user->u_mutex);
+	if (user->u_state != SMB_USER_STATE_LOGGING_ON) {
+		mutex_exit(&user->u_mutex);
+		smb_request_free(sr);
+		return;
+	}
+	/* smb_user_hold_internal */
+	user->u_refcnt++;
+	mutex_exit(&user->u_mutex);
+
+	/*
+	 * The user hold is given to the SR, and released in
+	 * smb_user_logoff_tq / smb_request_free
+	 */
+	sr->uid_user = user;
+	sr->user_cr = user->u_cred;
+	sr->sr_state = SMB_REQ_STATE_SUBMITTED;
+
+	(void) taskq_dispatch(
+	    user->u_server->sv_worker_pool,
+	    smb_user_logoff_tq, sr, TQ_SLEEP);
+}
+
+/*
+ * Helper for smb_user_auth_tmo()
+ */
+static void
+smb_user_logoff_tq(void *arg)
+{
+	smb_request_t	*sr = arg;
+
+	SMB_REQ_VALID(sr);
+
+	mutex_enter(&sr->sr_mutex);
+	sr->sr_worker = curthread;
+	sr->sr_state = SMB_REQ_STATE_ACTIVE;
+	mutex_exit(&sr->sr_mutex);
+
+	smb_user_logoff(sr->uid_user);
+
+	sr->sr_state = SMB_REQ_STATE_COMPLETED;
+	smb_request_free(sr);
+}
+
+/*
  * Determine whether or not the user is an administrator.
  * Members of the administrators group have administrative rights.
  */
@@ -523,23 +645,42 @@
  * Remove the user from the session's user list before freeing resources
  * associated with the user.
  */
-void
+static void
 smb_user_delete(void *arg)
 {
 	smb_session_t	*session;
 	smb_user_t	*user = (smb_user_t *)arg;
+	uint32_t	ucount;
 
 	SMB_USER_VALID(user);
 	ASSERT(user->u_refcnt == 0);
 	ASSERT(user->u_state == SMB_USER_STATE_LOGGED_OFF);
 	ASSERT(user->u_authsock == NULL);
+	ASSERT(user->u_auth_tmo == NULL);
 
 	session = user->u_session;
+
+	smb_server_dec_users(session->s_server);
 	smb_llist_enter(&session->s_user_list, RW_WRITER);
 	smb_llist_remove(&session->s_user_list, user);
 	smb_idpool_free(&session->s_uid_pool, user->u_uid);
+	ucount = smb_llist_get_count(&session->s_user_list);
 	smb_llist_exit(&session->s_user_list);
 
+	if (ucount == 0) {
+		smb_rwx_rwenter(&session->s_lock, RW_WRITER);
+		session->s_state = SMB_SESSION_STATE_SHUTDOWN;
+		smb_rwx_cvbcast(&session->s_lock);
+		smb_rwx_rwexit(&session->s_lock);
+	}
+
+	/*
+	 * This user is no longer on s_user_list, however...
+	 *
+	 * This is called via smb_llist_post, which means it may run
+	 * BEFORE smb_user_release drops u_mutex (if another thread
+	 * flushes the delete queue before we do).  Synchronize.
+	 */
 	mutex_enter(&user->u_mutex);
 	mutex_exit(&user->u_mutex);
 
@@ -673,7 +814,6 @@
 	info->ui_native_os = session->native_os;
 	info->ui_ipaddr = session->ipaddr;
 	info->ui_numopens = session->s_file_cnt;
-	info->ui_smb_uid = user->u_uid;
 	info->ui_logon_time = user->u_logon_time;
 	info->ui_flags = user->u_flags;
 	info->ui_posix_uid = crgetuid(user->u_cred);
@@ -707,11 +847,34 @@
 	bzero(info, sizeof (smb_netuserinfo_t));
 }
 
+/*
+ * Tell smbd this user is going away so it can clean up their
+ * audit session, autohome dir, etc.
+ *
+ * Note that when we're shutting down, smbd will already have set
+ * smbd.s_shutting_down and therefore will ignore door calls.
+ * Skip this during shutdown to reduce upcall noise.
+ */
 static void
 smb_user_auth_logoff(smb_user_t *user)
 {
-	uint32_t audit_sid = user->u_audit_sid;
+	smb_server_t *sv = user->u_server;
+	uint32_t audit_sid;
 
-	(void) smb_kdoor_upcall(user->u_server, SMB_DR_USER_AUTH_LOGOFF,
+	if (sv->sv_state != SMB_SERVER_STATE_RUNNING)
+		return;
+
+	audit_sid = user->u_audit_sid;
+	(void) smb_kdoor_upcall(sv, SMB_DR_USER_AUTH_LOGOFF,
 	    &audit_sid, xdr_uint32_t, NULL, NULL);
+}
+
+boolean_t
+smb_is_same_user(cred_t *cr1, cred_t *cr2)
+{
+	ksid_t *ks1 = crgetsid(cr1, KSID_USER);
+	ksid_t *ks2 = crgetsid(cr2, KSID_USER);
+
+	return (ks1->ks_rid == ks2->ks_rid &&
+	    strcmp(ks1->ks_domain->kd_name, ks2->ks_domain->kd_name) == 0);
 }
