From c9270ed720865d8fa748adf6908bcb9c8074c785 Mon Sep 17 00:00:00 2001
From: Noel Power <noel.power@suse.com>
Date: Mon, 20 Jul 2015 12:06:07 +0100
Subject: [PATCH] kerberos mount for SMB2, SMB3

Patch based on the information detailed in
http://thread.gmane.org/gmane.linux.kernel.cifs/10081/focus=10307
to enable SMB2/SMB3 kerberozied.

a) SMB2_negotiate: enable/use decode_negTokenInit in SMB2_negotiate
b) SMB2_sess_setup: handle Kerberos sectype and replicate Kerberos
   SMB1 processing done in sess_auth_kerberos

Signed-off-by: Noel Power <noel.power@suse.com>
---
 fs/cifs/smb2pdu.c | 153 +++++++++++++++++++++++++++++++++++-------------------
 1 file changed, 100 insertions(+), 53 deletions(-)

diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c
index b8b4f08..8833783 100644
--- a/fs/cifs/smb2pdu.c
+++ b/fs/cifs/smb2pdu.c
@@ -46,6 +46,7 @@
 #include "smb2status.h"
 #include "smb2glob.h"
 #include "cifspdu.h"
+#include "cifs_spnego.h"
 
 /*
  *  The following table defines the expected "StructureSize" of SMB2 requests
@@ -486,7 +487,6 @@ SMB2_negotiate(const unsigned int xid, struct cifs_ses *ses)
 		cifs_dbg(FYI, "missing security blob on negprot\n");
 
 	rc = cifs_enable_signing(server, ses->sign);
-#ifdef CONFIG_SMB2_ASN1  /* BB REMOVEME when updated asn1.c ready */
 	if (rc)
 		goto neg_exit;
 	if (blob_length)
@@ -497,7 +497,6 @@ SMB2_negotiate(const unsigned int xid, struct cifs_ses *ses)
 		rc = -EIO;
 		goto neg_exit;
 	}
-#endif
 
 neg_exit:
 	free_rsp_buf(resp_buftype, rsp);
@@ -592,9 +591,10 @@ SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses,
 	__le32 phase = NtLmNegotiate; /* NTLMSSP, if needed, is multistage */
 	struct TCP_Server_Info *server = ses->server;
 	u16 blob_length = 0;
-	char *security_blob;
+	char *security_blob = NULL;
 	char *ntlmssp_blob = NULL;
 	bool use_spnego = false; /* else use raw ntlmssp */
+	struct key *spnego_key = NULL;
 
 	cifs_dbg(FYI, "Session Setup\n");
 
@@ -620,7 +620,8 @@ SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses,
 	ses->ntlmssp->sesskey_per_smbsess = true;
 
 	/* FIXME: allow for other auth types besides NTLMSSP (e.g. krb5) */
-	ses->sectype = RawNTLMSSP;
+	if (ses->sectype != Kerberos && ses->sectype != RawNTLMSSP)
+		ses->sectype = RawNTLMSSP;
 
 ssetup_ntlmssp_authenticate:
 	if (phase == NtLmChallenge)
@@ -649,60 +650,104 @@ ssetup_ntlmssp_authenticate:
 	iov[0].iov_base = (char *)req;
 	/* 4 for rfc1002 length field and 1 for pad */
 	iov[0].iov_len = get_rfc1002_length(req) + 4 - 1;
-	if (phase == NtLmNegotiate) {
-		ntlmssp_blob = kmalloc(sizeof(struct _NEGOTIATE_MESSAGE),
-				       GFP_KERNEL);
-		if (ntlmssp_blob == NULL) {
-			rc = -ENOMEM;
+
+	if (ses->sectype == RawNTLMSSP) {
+		if (phase == NtLmNegotiate) {
+			ntlmssp_blob =
+				kmalloc(sizeof(struct _NEGOTIATE_MESSAGE),
+					       GFP_KERNEL);
+			if (ntlmssp_blob == NULL) {
+				rc = -ENOMEM;
+				goto ssetup_exit;
+			}
+			build_ntlmssp_negotiate_blob(ntlmssp_blob, ses);
+			if (use_spnego) {
+				/* blob_length = build_spnego_ntlmssp_blob(
+						&security_blob,
+						sizeof(struct _NEGOTIATE_MESSAGE),
+						ntlmssp_blob); */
+				/* BB eventually need to add this */
+				cifs_dbg(VFS,
+					"spnego not supported for SMB2 yet\n");
+				rc = -EOPNOTSUPP;
+				kfree(ntlmssp_blob);
+				goto ssetup_exit;
+			} else {
+				blob_length = sizeof(struct _NEGOTIATE_MESSAGE);
+				/* with raw NTLMSSP we don't encapsulate in SPNEGO */
+				security_blob = ntlmssp_blob;
+			}
+		} else if (phase == NtLmAuthenticate) {
+			req->hdr.SessionId = ses->Suid;
+			ntlmssp_blob =
+				kzalloc(sizeof(struct _NEGOTIATE_MESSAGE) + 500,
+					       GFP_KERNEL);
+			if (ntlmssp_blob == NULL) {
+				rc = -ENOMEM;
+				goto ssetup_exit;
+			}
+			rc = build_ntlmssp_auth_blob(ntlmssp_blob, &blob_length,
+						     ses, nls_cp);
+			if (rc) {
+				cifs_dbg(FYI,
+					 "build_ntlmssp_auth_blob failed %d\n",
+					 rc);
+				goto ssetup_exit; /* BB double check error handling */
+			}
+			if (use_spnego) {
+				/* blob_length = build_spnego_ntlmssp_blob(
+								&security_blob,
+								blob_length,
+								ntlmssp_blob); */
+				cifs_dbg(VFS,
+					 "spnego not supported for SMB2 yet\n");
+				rc = -EOPNOTSUPP;
+				kfree(ntlmssp_blob);
+				goto ssetup_exit;
+			} else {
+				security_blob = ntlmssp_blob;
+			}
+		} else {
+			cifs_dbg(VFS, "illegal ntlmssp phase\n");
+			rc = -EIO;
 			goto ssetup_exit;
 		}
-		build_ntlmssp_negotiate_blob(ntlmssp_blob, ses);
-		if (use_spnego) {
-			/* blob_length = build_spnego_ntlmssp_blob(
-					&security_blob,
-					sizeof(struct _NEGOTIATE_MESSAGE),
-					ntlmssp_blob); */
-			/* BB eventually need to add this */
-			cifs_dbg(VFS, "spnego not supported for SMB2 yet\n");
-			rc = -EOPNOTSUPP;
-			kfree(ntlmssp_blob);
+		iov[1].iov_base = security_blob;
+		iov[1].iov_len = blob_length;
+	} else if (ses->sectype == Kerberos) {
+		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;
-		} else {
-			blob_length = sizeof(struct _NEGOTIATE_MESSAGE);
-			/* with raw NTLMSSP we don't encapsulate in SPNEGO */
-			security_blob = ntlmssp_blob;
 		}
-	} else if (phase == NtLmAuthenticate) {
-		req->hdr.SessionId = ses->Suid;
-		ntlmssp_blob = kzalloc(sizeof(struct _NEGOTIATE_MESSAGE) + 500,
-				       GFP_KERNEL);
-		if (ntlmssp_blob == NULL) {
-			rc = -ENOMEM;
+
+		msg = spnego_key->payload.data;
+		/* check version field to make sure that cifs.upcall is
+		   sending us a response in an expected form */
+		if (msg->version != CIFS_SPNEGO_UPCALL_VERSION) {
+			cifs_dbg(VFS, "incorrect version of cifs.upcall "
+				   "expected %d but got %d)",
+				   CIFS_SPNEGO_UPCALL_VERSION, msg->version);
+			rc = -EKEYREJECTED;
 			goto ssetup_exit;
 		}
-		rc = build_ntlmssp_auth_blob(ntlmssp_blob, &blob_length, ses,
-					     nls_cp);
-		if (rc) {
-			cifs_dbg(FYI, "build_ntlmssp_auth_blob failed %d\n",
-				 rc);
-			goto ssetup_exit; /* BB double check error handling */
-		}
-		if (use_spnego) {
-			/* blob_length = build_spnego_ntlmssp_blob(
-							&security_blob,
-							blob_length,
-							ntlmssp_blob); */
-			cifs_dbg(VFS, "spnego not supported for SMB2 yet\n");
-			rc = -EOPNOTSUPP;
-			kfree(ntlmssp_blob);
+
+		ses->auth_key.response = kmemdup(msg->data, msg->sesskey_len,
+						 GFP_KERNEL);
+		if (!ses->auth_key.response) {
+			cifs_dbg(VFS,
+				"Kerberos can't allocate (%u bytes) memory",
+				msg->sesskey_len);
+			rc = -ENOMEM;
 			goto ssetup_exit;
-		} else {
-			security_blob = ntlmssp_blob;
 		}
-	} else {
-		cifs_dbg(VFS, "illegal ntlmssp phase\n");
-		rc = -EIO;
-		goto ssetup_exit;
+		ses->auth_key.len = msg->sesskey_len;
+		blob_length = msg->secblob_len;
+		iov[1].iov_base = msg->data + msg->sesskey_len;;
+		iov[1].iov_len = blob_length;
 	}
 
 	/* Testing shows that buffer offset must be at location of Buffer[0] */
@@ -710,8 +755,6 @@ ssetup_ntlmssp_authenticate:
 				cpu_to_le16(sizeof(struct smb2_sess_setup_req) -
 					    1 /* pad */ - 4 /* rfc1001 len */);
 	req->SecurityBufferLength = cpu_to_le16(blob_length);
-	iov[1].iov_base = security_blob;
-	iov[1].iov_len = blob_length;
 
 	inc_rfc1001_len(req, blob_length - 1 /* pad */);
 
@@ -722,6 +765,7 @@ ssetup_ntlmssp_authenticate:
 
 	kfree(security_blob);
 	rsp = (struct smb2_sess_setup_rsp *)iov[0].iov_base;
+	ses->Suid = rsp->hdr.SessionId;
 	if (resp_buftype != CIFS_NO_BUFFER &&
 	    rsp->hdr.Status == STATUS_MORE_PROCESSING_REQUIRED) {
 		if (phase != NtLmNegotiate) {
@@ -739,7 +783,6 @@ ssetup_ntlmssp_authenticate:
 		/* NTLMSSP Negotiate sent now processing challenge (response) */
 		phase = NtLmChallenge; /* process ntlmssp challenge */
 		rc = 0; /* MORE_PROCESSING is not an error here but expected */
-		ses->Suid = rsp->hdr.SessionId;
 		rc = decode_ntlmssp_challenge(rsp->Buffer,
 				le16_to_cpu(rsp->SecurityBufferLength), ses);
 	}
@@ -796,6 +839,10 @@ keygen_exit:
 		kfree(ses->auth_key.response);
 		ses->auth_key.response = NULL;
 	}
+	if (spnego_key) {
+		key_invalidate(spnego_key);
+		key_put(spnego_key);
+	}
 	kfree(ses->ntlmssp);
 
 	return rc;
-- 
1.8.5.6

