--- old/usr/src/uts/common/fs/smbsrv/smb_authenticate.c	Mon Aug  5 17:26:26 2019
+++ new/usr/src/uts/common/fs/smbsrv/smb_authenticate.c	Mon Aug  5 17:26:26 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.
  */
 
 /*
@@ -37,10 +37,10 @@
 #include <smbsrv/smb_kproto.h>
 #include <smbsrv/smb_token.h>
 
-static uint32_t smb_authsock_open(smb_user_t *);
+static uint32_t smb_authsock_open(smb_request_t *);
 static int smb_authsock_send(ksocket_t, void *, size_t);
 static int smb_authsock_recv(ksocket_t, void *, size_t);
-static uint32_t smb_authsock_sendrecv(smb_user_t *, smb_lsa_msg_hdr_t *hdr,
+static uint32_t smb_authsock_sendrecv(smb_request_t *, smb_lsa_msg_hdr_t *hdr,
 				void *sndbuf, void **recvbuf);
 /* void smb_authsock_close(smb_user_t *); kproto.h */
 
@@ -51,6 +51,7 @@
 
 /*
  * Handle old-style session setup (non-extended security)
+ * Note: Used only by SMB1
  *
  * The user information is passed to smbd for authentication.
  * If smbd can authenticate the user an access token is returned and we
@@ -69,6 +70,7 @@
 	/* user cleanup in smb_request_free */
 	sr->uid_user = user;
 	sr->smb_uid = user->u_uid;
+	sr->smb2_ssnid = 0;
 
 	/*
 	 * Open a connection to the local logon service.
@@ -75,7 +77,7 @@
 	 * If we can't, it may be busy, or not running.
 	 * Don't log here - this may be frequent.
 	 */
-	if ((status = smb_authsock_open(user)) != 0)
+	if ((status = smb_authsock_open(sr)) != 0)
 		goto errout;
 
 	/*
@@ -114,7 +116,6 @@
 	smb_logon_t	user_info;
 	XDR		xdrs;
 	smb_arg_sessionsetup_t *sinfo = sr->sr_ssetup;
-	smb_user_t	*user = sr->uid_user;
 	void		*sbuf = NULL;
 	void		*rbuf = NULL;
 	uint32_t	slen = 0;
@@ -153,7 +154,7 @@
 
 	msg_hdr.lmh_msgtype = LSA_MTYPE_OLDREQ;
 	msg_hdr.lmh_msglen = slen;
-	status = smb_authsock_sendrecv(user, &msg_hdr, sbuf, &rbuf);
+	status = smb_authsock_sendrecv(sr, &msg_hdr, sbuf, &rbuf);
 	if (status != 0)
 		goto out;
 	rlen = msg_hdr.lmh_msglen;
@@ -232,13 +233,29 @@
 	ASSERT(sr->uid_user == NULL);
 
 	/*
-	 * On the first request (UID==0) create a USER object.
-	 * On subsequent requests (UID!=0) find the USER object.
+	 * Paranoid:  While finding/creating the user object, make sure
+	 * SMB2 ignores smb_uid, and SMB1 ignores smb2_ssnid.  The
+	 * logic below assumes the "other" one is always zero; both
+	 * the "first request" tests and smb_session_lookup_uid_st.
+	 */
+	if (sr->session->dialect >= SMB_VERS_2_BASE) {
+		/* SMB2+ ignores smb_uid */
+		ASSERT(sr->smb_uid == 0);
+		sr->smb_uid = 0;
+	} else {
+		/* SMB1 ignores smb2_ssnid */
+		ASSERT(sr->smb2_ssnid == 0);
+		sr->smb2_ssnid = 0;
+	}
+
+	/*
+	 * On the first request (UID/ssnid==0) create a USER object.
+	 * On subsequent requests (UID/ssnid!=0) find the USER object.
 	 * Either way, sr->uid_user is set, so our ref. on the
 	 * user object is dropped during normal cleanup work
 	 * for the smb_request (sr).  Ditto u_authsock.
 	 */
-	if (sr->smb_uid == 0) {
+	if (sr->smb2_ssnid == 0 && sr->smb_uid == 0) {
 		user = smb_user_new(sr->session);
 		if (user == NULL)
 			return (NT_STATUS_TOO_MANY_SESSIONS);
@@ -245,7 +262,13 @@
 
 		/* user cleanup in smb_request_free */
 		sr->uid_user = user;
-		sr->smb_uid = user->u_uid;
+		if (sr->session->dialect >= SMB_VERS_2_BASE) {
+			/* Intentionally leave smb_uid=0 for SMB2 */
+			sr->smb2_ssnid = user->u_ssnid;
+		} else {
+			/* Intentionally leave smb2_ssnid=0 for SMB1 */
+			sr->smb_uid = user->u_uid;
+		}
 
 		/*
 		 * Open a connection to the local logon service.
@@ -252,7 +275,7 @@
 		 * If we can't, it may be busy, or not running.
 		 * Don't log here - this may be frequent.
 		 */
-		if ((status = smb_authsock_open(user)) != 0)
+		if ((status = smb_authsock_open(sr)) != 0)
 			goto errout;
 
 		/*
@@ -264,7 +287,7 @@
 		msg_hdr.lmh_msgtype = LSA_MTYPE_ESFIRST;
 	} else {
 		user = smb_session_lookup_uid_st(sr->session,
-		    sr->smb_uid, SMB_USER_STATE_LOGGING_ON);
+		    sr->smb2_ssnid, sr->smb_uid, SMB_USER_STATE_LOGGING_ON);
 		if (user == NULL)
 			return (NT_STATUS_USER_SESSION_DELETED);
 
@@ -280,7 +303,7 @@
 	 * and send it up the authsock with either
 	 */
 	msg_hdr.lmh_msglen = sinfo->ssi_iseclen;
-	status = smb_authsock_sendrecv(user, &msg_hdr,
+	status = smb_authsock_sendrecv(sr, &msg_hdr,
 	    sinfo->ssi_isecblob, &rbuf);
 	if (status != 0)
 		goto errout;
@@ -350,7 +373,6 @@
 {
 	smb_lsa_msg_hdr_t msg_hdr;
 	smb_lsa_clinfo_t clinfo;
-	smb_user_t *user = sr->uid_user;
 	void *rbuf = NULL;
 	uint32_t status;
 
@@ -364,7 +386,7 @@
 	(void) memcpy(clinfo.lci_challenge_key,
 	    sr->session->challenge_key,
 	    sizeof (clinfo.lci_challenge_key));
-	status = smb_authsock_sendrecv(user, &msg_hdr, &clinfo, &rbuf);
+	status = smb_authsock_sendrecv(sr, &msg_hdr, &clinfo, &rbuf);
 	/* We don't use this response. */
 	if (rbuf != NULL) {
 		kmem_free(rbuf, msg_hdr.lmh_msglen);
@@ -390,13 +412,12 @@
 	uint32_t	rlen = 0;
 	uint32_t	privileges;
 	uint32_t	status;
-	int		rc;
 	bool_t		ok;
 
 	msg_hdr.lmh_msgtype = LSA_MTYPE_GETTOK;
 	msg_hdr.lmh_msglen = 0;
 
-	status = smb_authsock_sendrecv(user, &msg_hdr, NULL, &rbuf);
+	status = smb_authsock_sendrecv(sr, &msg_hdr, NULL, &rbuf);
 	if (status != 0)
 		goto errout;
 
@@ -437,7 +458,7 @@
 	/*
 	 * Setup the logon object.
 	 */
-	cr = smb_cred_create(token);
+	cr = smb_cred_create(token, sr->session);
 	if (cr == NULL)
 		goto errout;
 	privileges = smb_priv_xlate(token);
@@ -447,19 +468,31 @@
 	crfree(cr);
 
 	/*
+	 * Some basic processing for encryption needs to be done,
+	 * even for anonymous/guest sessions. In particular,
+	 * we need to set Session.EncryptData.
+	 *
+	 * Windows handling of anon/guest and encryption is strange.
+	 * It allows these accounts to get through session setup,
+	 * even when they provide no key material.
+	 * Additionally, Windows somehow manages to have key material
+	 * for anonymous accounts under unknown circumstances.
+	 * As such, We set EncryptData on anon/guest to behave like Windows,
+	 * at least through Session Setup.
+	 */
+	if (sr->session->dialect >= SMB_VERS_3_0)
+		smb3_encrypt_begin(sr, token);
+
+	/*
 	 * Save the session key, and (maybe) enable signing,
 	 * but only for real logon (not ANON or GUEST).
 	 */
 	if ((token->tkn_flags & (SMB_ATF_GUEST | SMB_ATF_ANON)) == 0) {
 		if (sr->session->dialect >= SMB_VERS_2_BASE) {
-			rc = smb2_sign_begin(sr, token);
+			smb2_sign_begin(sr, token);
 		} else {
-			rc = smb_sign_begin(sr, token);
+			smb_sign_begin(sr, token);
 		}
-		if (rc != 0) {
-			status = NT_STATUS_INTERNAL_ERROR;
-			goto errout;
-		}
 	}
 
 	smb_token_free(token);
@@ -496,6 +529,12 @@
 {
 	uint32_t	privileges = 0;
 
+	if (smb_token_query_privilege(token, SE_SECURITY_LUID))
+		privileges |= SMB_USER_PRIV_SECURITY;
+
+	if (smb_token_query_privilege(token, SE_TAKE_OWNERSHIP_LUID))
+		privileges |= SMB_USER_PRIV_TAKE_OWNERSHIP;
+
 	if (smb_token_query_privilege(token, SE_BACKUP_LUID))
 		privileges |= SMB_USER_PRIV_BACKUP;
 
@@ -502,16 +541,44 @@
 	if (smb_token_query_privilege(token, SE_RESTORE_LUID))
 		privileges |= SMB_USER_PRIV_RESTORE;
 
-	if (smb_token_query_privilege(token, SE_TAKE_OWNERSHIP_LUID))
-		privileges |= SMB_USER_PRIV_TAKE_OWNERSHIP;
+	if (smb_token_query_privilege(token, SE_CHANGE_NOTIFY_LUID))
+		privileges |= SMB_USER_PRIV_CHANGE_NOTIFY;
 
-	if (smb_token_query_privilege(token, SE_SECURITY_LUID))
-		privileges |= SMB_USER_PRIV_SECURITY;
-
 	return (privileges);
 }
 
 /*
+ * Unblock a request that might be blocked reading some
+ * authentication socket.  This can happen when either the
+ * client cancels a session setup or closes the connection.
+ */
+static void
+smb_authsock_cancel(smb_request_t *sr)
+{
+	smb_user_t *user = sr->cancel_arg2;
+	ksocket_t authsock = NULL;
+
+	if (user == NULL)
+		return;
+	ASSERT(user == sr->uid_user);
+
+	/*
+	 * Check user state, and get a hold on the auth socket.
+	 */
+	mutex_enter(&user->u_mutex);
+	if (user->u_state == SMB_USER_STATE_LOGGING_ON) {
+		if ((authsock = user->u_authsock) != NULL)
+			ksocket_hold(authsock);
+	}
+	mutex_exit(&user->u_mutex);
+
+	if (authsock != NULL) {
+		(void) ksocket_shutdown(authsock, SHUT_RDWR, sr->user_cr);
+		ksocket_rele(authsock);
+	}
+}
+
+/*
  * Send/recv a request/reply sequence on the auth socket.
  * Returns zero or an NT status.
  *
@@ -519,9 +586,10 @@
  * With limited authsock instances, this should be rare.
  */
 static uint32_t
-smb_authsock_sendrecv(smb_user_t *user, smb_lsa_msg_hdr_t *hdr,
-	void *sndbuf, void **recvbuf)
+smb_authsock_sendrecv(smb_request_t *sr, smb_lsa_msg_hdr_t *hdr,
+    void *sndbuf, void **recvbuf)
 {
+	smb_user_t *user = sr->uid_user;
 	ksocket_t so;
 	uint32_t status;
 	int rc;
@@ -538,25 +606,28 @@
 	ksocket_hold(so);
 	mutex_exit(&user->u_mutex);
 
+	mutex_enter(&sr->sr_mutex);
+	if (sr->sr_state != SMB_REQ_STATE_ACTIVE) {
+		mutex_exit(&sr->sr_mutex);
+		status = NT_STATUS_CANCELLED;
+		goto out;
+	}
+	sr->sr_state = SMB_REQ_STATE_WAITING_AUTH;
+	sr->cancel_method = smb_authsock_cancel;
+	sr->cancel_arg2 = user;
+	mutex_exit(&sr->sr_mutex);
+
 	rc = smb_authsock_send(so, hdr, sizeof (*hdr));
 	if (rc == 0 && hdr->lmh_msglen != 0) {
 		rc = smb_authsock_send(so, sndbuf, hdr->lmh_msglen);
 	}
-	if (rc)
-		goto out;
-
-	rc = smb_authsock_recv(so, hdr, sizeof (*hdr));
+	if (rc == 0)
+		rc = smb_authsock_recv(so, hdr, sizeof (*hdr));
 	if (rc == 0 && hdr->lmh_msglen != 0) {
 		*recvbuf = kmem_alloc(hdr->lmh_msglen, KM_SLEEP);
 		rc = smb_authsock_recv(so, *recvbuf, hdr->lmh_msglen);
-		if (rc) {
-			kmem_free(*recvbuf, hdr->lmh_msglen);
-			*recvbuf = NULL;
-		}
 	}
 
-out:
-	ksocket_rele(so);
 	switch (rc) {
 	case 0:
 		status = 0;
@@ -572,6 +643,30 @@
 		break;
 	}
 
+	mutex_enter(&sr->sr_mutex);
+	sr->cancel_method = NULL;
+	sr->cancel_arg2 = NULL;
+	switch (sr->sr_state) {
+	case SMB_REQ_STATE_WAITING_AUTH:
+		sr->sr_state = SMB_REQ_STATE_ACTIVE;
+		break;
+	case SMB_REQ_STATE_CANCEL_PENDING:
+		sr->sr_state = SMB_REQ_STATE_CANCELLED;
+		status = NT_STATUS_CANCELLED;
+		break;
+	default:
+		status = NT_STATUS_INTERNAL_ERROR;
+		break;
+	}
+	mutex_exit(&sr->sr_mutex);
+
+out:
+	ksocket_rele(so);
+
+	if (status != 0 && *recvbuf != NULL) {
+		kmem_free(*recvbuf, hdr->lmh_msglen);
+		*recvbuf = NULL;
+	}
 	return (status);
 }
 
@@ -593,17 +688,28 @@
  */
 struct timeval smb_auth_send_tmo = { 15, 0 };
 
+/*
+ * Maximum time a user object may stay in state LOGGING_ON
+ */
+int smb_auth_total_tmo = 45;	/* seconds */
+
 static uint32_t
-smb_authsock_open(smb_user_t *user)
+smb_authsock_open(smb_request_t *sr)
 {
-	smb_server_t *sv = user->u_server;
+	smb_user_t *user = sr->uid_user;
+	smb_server_t *sv = sr->sr_server;
 	ksocket_t so = NULL;
-	uint32_t status;
+	uint32_t status = 0;
 	int rc;
 
 	/*
-	 * If the auth. service is busy, wait our turn.
-	 * This may be frequent, so don't log.
+	 * If the auth. service is busy, wait our turn.  This threshold
+	 * limits the number of auth sockets we might have trying to
+	 * communicate with the auth. service up in smbd.  Until we've
+	 * set u_authsock, we need to "exit this threshold" in any
+	 * error code paths after this "enter".
+	 *
+	 * Failure to "enter" may be frequent, so don't log.
 	 */
 	if ((rc = smb_threshold_enter(&sv->sv_ssetup_ct)) != 0)
 		return (NT_STATUS_NO_LOGON_SERVERS);
@@ -612,11 +718,34 @@
 	    KSOCKET_SLEEP, CRED());
 	if (rc != 0) {
 		cmn_err(CE_NOTE, "smb_authsock_open: socket, rc=%d", rc);
+		smb_threshold_exit(&sv->sv_ssetup_ct);
 		status = NT_STATUS_INSUFF_SERVER_RESOURCES;
 		goto errout;
 	}
 
 	/*
+	 * This (new) user object now gets an authsocket.
+	 * Note: u_authsock cleanup in smb_user_logoff.
+	 * After we've set u_authsock, smb_threshold_exit
+	 * is done in smb_authsock_close().  If we somehow
+	 * already have an authsock, close the new one and
+	 * error out.
+	 */
+	mutex_enter(&user->u_mutex);
+	if (user->u_authsock != NULL) {
+		mutex_exit(&user->u_mutex);
+		smb_authsock_close(user, so);
+		status = NT_STATUS_INTERNAL_ERROR;
+		goto errout;
+	}
+	user->u_authsock = so;
+	if (smb_auth_total_tmo != 0) {
+		user->u_auth_tmo = timeout(smb_user_auth_tmo, user,
+		    SEC_TO_TICK(smb_auth_total_tmo));
+	}
+	mutex_exit(&user->u_mutex);
+
+	/*
 	 * Set the send/recv timeouts.
 	 */
 	(void) ksocket_setsockopt(so, SOL_SOCKET, SO_SNDTIMEO,
@@ -630,30 +759,42 @@
 	 * Would like to set the connect timeout too, but there's
 	 * apparently no easy way to do that for AF_UNIX.
 	 */
+	mutex_enter(&sr->sr_mutex);
+	if (sr->sr_state != SMB_REQ_STATE_ACTIVE) {
+		mutex_exit(&sr->sr_mutex);
+		status = NT_STATUS_CANCELLED;
+		goto errout;
+	}
+	sr->sr_state = SMB_REQ_STATE_WAITING_AUTH;
+	sr->cancel_method = smb_authsock_cancel;
+	sr->cancel_arg2 = user;
+	mutex_exit(&sr->sr_mutex);
+
 	rc = ksocket_connect(so, (struct sockaddr *)&smbauth_sockname,
 	    sizeof (smbauth_sockname), CRED());
 	if (rc != 0) {
 		DTRACE_PROBE1(error, int, rc);
 		status = NT_STATUS_NETLOGON_NOT_STARTED;
-		goto errout;
 	}
 
-	/* Note: u_authsock cleanup in smb_authsock_close() */
-	mutex_enter(&user->u_mutex);
-	if (user->u_authsock != NULL) {
-		mutex_exit(&user->u_mutex);
+	mutex_enter(&sr->sr_mutex);
+	sr->cancel_method = NULL;
+	sr->cancel_arg2 = NULL;
+	switch (sr->sr_state) {
+	case SMB_REQ_STATE_WAITING_AUTH:
+		sr->sr_state = SMB_REQ_STATE_ACTIVE;
+		break;
+	case SMB_REQ_STATE_CANCEL_PENDING:
+		sr->sr_state = SMB_REQ_STATE_CANCELLED;
+		status = NT_STATUS_CANCELLED;
+		break;
+	default:
 		status = NT_STATUS_INTERNAL_ERROR;
-		goto errout;
+		break;
 	}
-	user->u_authsock = so;
-	mutex_exit(&user->u_mutex);
-	return (0);
+	mutex_exit(&sr->sr_mutex);
 
 errout:
-	if (so != NULL)
-		(void) ksocket_close(so, CRED());
-	smb_threshold_exit(&sv->sv_ssetup_ct);
-
 	return (status);
 }
 
@@ -699,14 +840,16 @@
 	return (rc);
 }
 
+/*
+ * Caller has cleared user->u_authsock, passing the last ref
+ * as the 2nd arg here.  This can block, so it's called
+ * after exiting u_mutex.
+ */
 void
-smb_authsock_close(smb_user_t *user)
+smb_authsock_close(smb_user_t *user, ksocket_t so)
 {
 
-	ASSERT(MUTEX_HELD(&user->u_mutex));
-	if (user->u_authsock == NULL)
-		return;
-	(void) ksocket_close(user->u_authsock, CRED());
-	user->u_authsock = NULL;
+	(void) ksocket_shutdown(so, SHUT_RDWR, CRED());
+	(void) ksocket_close(so, CRED());
 	smb_threshold_exit(&user->u_server->sv_ssetup_ct);
 }
