[CIFS] Have CIFS_SessSetup build correct SPNEGO SessionSetup request

Have CIFS_SessSetup call cifs_get_spnego_key when Kerberos is
negotiated. Use the info in the key payload to build a session
setup request packet. Also clean up how the request buffer in
the function is freed on error.

With appropriate user space helper (in samba/source/client). Kerberos
support (secure session establishment can be done now via Kerberos,
previously users would have to use NTLMv2 instead for more secure
session setup).

Signed-off-by: Jeff Layton <jlayton@redhat.com>
Signed-off-by: Steve French <sfrench@us.ibm.com>
diff --git a/fs/cifs/sess.c b/fs/cifs/sess.c
index ed01ef3..d0cb469 100644
--- a/fs/cifs/sess.c
+++ b/fs/cifs/sess.c
@@ -29,6 +29,7 @@
 #include "ntlmssp.h"
 #include "nterr.h"
 #include <linux/utsname.h>
+#include "cifs_spnego.h"
 
 extern void SMBNTencrypt(unsigned char *passwd, unsigned char *c8,
 			 unsigned char *p24);
@@ -340,11 +341,12 @@
 	SESSION_SETUP_ANDX *pSMB;
 	__u32 capabilities;
 	int count;
-	int resp_buf_type = 0;
-	struct kvec iov[2];
+	int resp_buf_type;
+	struct kvec iov[3];
 	enum securityEnum type;
 	__u16 action;
 	int bytes_remaining;
+	struct key *spnego_key = NULL;
 
 	if (ses == NULL)
 		return -EINVAL;
@@ -377,24 +379,32 @@
 
 	capabilities = cifs_ssetup_hdr(ses, pSMB);
 
-	/* we will send the SMB in two pieces,
-	a fixed length beginning part, and a
-	second part which will include the strings
-	and rest of bcc area, in order to avoid having
-	to do a large buffer 17K allocation */
+	/* we will send the SMB in three pieces:
+	a fixed length beginning part, an optional
+	SPNEGO blob (which can be zero length), and a
+	last part which will include the strings
+	and rest of bcc area. This allows us to avoid
+	a large buffer 17K allocation */
 	iov[0].iov_base = (char *)pSMB;
 	iov[0].iov_len = smb_buf->smb_buf_length + 4;
 
+	/* setting this here allows the code at the end of the function
+	   to free the request buffer if there's an error */
+	resp_buf_type = CIFS_SMALL_BUFFER;
+
 	/* 2000 big enough to fit max user, domain, NOS name etc. */
 	str_area = kmalloc(2000, GFP_KERNEL);
 	if (str_area == NULL) {
-		cifs_small_buf_release(smb_buf);
-		return -ENOMEM;
+		rc = -ENOMEM;
+		goto ssetup_exit;
 	}
 	bcc_ptr = str_area;
 
 	ses->flags &= ~CIFS_SES_LANMAN;
 
+	iov[1].iov_base = NULL;
+	iov[1].iov_len = 0;
+
 	if (type == LANMAN) {
 #ifdef CONFIG_CIFS_WEAK_PW_HASH
 		char lnm_session_key[CIFS_SESS_KEY_SIZE];
@@ -463,8 +473,8 @@
 		   struct ntlmv2_resp */
 
 		if (v2_sess_key == NULL) {
-			cifs_small_buf_release(smb_buf);
-			return -ENOMEM;
+			rc = -ENOMEM;
+			goto ssetup_exit;
 		}
 
 		pSMB->req_no_secext.Capabilities = cpu_to_le32(capabilities);
@@ -499,21 +509,66 @@
 			unicode_ssetup_strings(&bcc_ptr, ses, nls_cp);
 		} else
 			ascii_ssetup_strings(&bcc_ptr, ses, nls_cp);
-	} else /* NTLMSSP or SPNEGO */ {
+	} else if (type == Kerberos) {
+#ifdef CONFIG_CIFS_UPCALL
+		struct cifs_spnego_msg *msg;
+		spnego_key = cifs_get_spnego_key(ses);
+		if (IS_ERR(spnego_key)) {
+			rc = PTR_ERR(spnego_key);
+			spnego_key = NULL;
+			goto ssetup_exit;
+		}
+
+		msg = spnego_key->payload.data;
+		/* bail out if key is too long */
+		if (msg->sesskey_len >
+		    sizeof(ses->server->mac_signing_key.data.krb5)) {
+			cERROR(1, ("Kerberos signing key too long (%u bytes)",
+				msg->sesskey_len));
+			rc = -EOVERFLOW;
+			goto ssetup_exit;
+		}
+		ses->server->mac_signing_key.len = msg->sesskey_len;
+		memcpy(ses->server->mac_signing_key.data.krb5, msg->data,
+			msg->sesskey_len);
 		pSMB->req.hdr.Flags2 |= SMBFLG2_EXT_SEC;
 		capabilities |= CAP_EXTENDED_SECURITY;
 		pSMB->req.Capabilities = cpu_to_le32(capabilities);
-		/* BB set password lengths */
+		iov[1].iov_base = msg->data + msg->sesskey_len;
+		iov[1].iov_len = msg->secblob_len;
+		pSMB->req.SecurityBlobLength = cpu_to_le16(iov[1].iov_len);
+
+		if (ses->capabilities & CAP_UNICODE) {
+			/* unicode strings must be word aligned */
+			if (iov[0].iov_len % 2) {
+				*bcc_ptr = 0;
+				bcc_ptr++;
+			}
+			unicode_oslm_strings(&bcc_ptr, nls_cp);
+			unicode_domain_string(&bcc_ptr, ses, nls_cp);
+		} else
+		/* BB: is this right? */
+			ascii_ssetup_strings(&bcc_ptr, ses, nls_cp);
+#else /* ! CONFIG_CIFS_UPCALL */
+		cERROR(1, ("Kerberos negotiated but upcall support disabled!"));
+		rc = -ENOSYS;
+		goto ssetup_exit;
+#endif /* CONFIG_CIFS_UPCALL */
+	} else {
+		cERROR(1, ("secType %d not supported!", type));
+		rc = -ENOSYS;
+		goto ssetup_exit;
 	}
 
-	count = (long) bcc_ptr - (long) str_area;
+	iov[2].iov_base = str_area;
+	iov[2].iov_len = (long) bcc_ptr - (long) str_area;
+
+	count = iov[1].iov_len + iov[2].iov_len;
 	smb_buf->smb_buf_length += count;
 
 	BCC_LE(smb_buf) = cpu_to_le16(count);
 
-	iov[1].iov_base = str_area;
-	iov[1].iov_len = count;
-	rc = SendReceive2(xid, ses, iov, 2 /* num_iovecs */, &resp_buf_type,
+	rc = SendReceive2(xid, ses, iov, 3 /* num_iovecs */, &resp_buf_type,
 			  CIFS_STD_OP /* not long */ | CIFS_LOG_ERROR);
 	/* SMB request buf freed in SendReceive2 */
 
@@ -560,6 +615,8 @@
 					 ses, nls_cp);
 
 ssetup_exit:
+	if (spnego_key)
+		key_put(spnego_key);
 	kfree(str_area);
 	if (resp_buf_type == CIFS_SMALL_BUFFER) {
 		cFYI(1, ("ssetup freeing small buf %p", iov[0].iov_base));