CIFSGetDFSRefer cleanup + dfs_referral_level_3 fixed to conform REFERRAL_V3 the MS-DFSC spec.

Signed-off-by: Igor Mammedov <niallain@gmail.com>
Signed-off-by: Steve French <sfrench@us.ibm.com>
diff --git a/fs/cifs/cifspdu.h b/fs/cifs/cifspdu.h
index c43bf4b..93d5ee0 100644
--- a/fs/cifs/cifspdu.h
+++ b/fs/cifs/cifspdu.h
@@ -1906,17 +1906,15 @@
 
 typedef struct dfs_referral_level_3 {
 	__le16 VersionNumber;
-	__le16 ReferralSize;
-	__le16 ServerType;	/* 0x0001 = CIFS server */
-	__le16 ReferralFlags;	/* or proximity - not clear which since it is
-				   always set to zero - SNIA spec says 0x01
-				   means strip off PathConsumed chars before
-				   submitting RequestFileName to remote node */
-	__le16 TimeToLive;
-	__le16 Proximity;
+	__le16 Size;
+	__le16 ServerType; /* 0x0001 = root targets; 0x0000 = link targets */
+	__le16 ReferralEntryFlags; /* 0x0200 bit set only for domain
+				      or DC referral responce */
+	__le32 TimeToLive;
 	__le16 DfsPathOffset;
 	__le16 DfsAlternatePathOffset;
-	__le16 NetworkAddressOffset;
+	__le16 NetworkAddressOffset; /* offset of the link target */
+	__le16 ServiceSiteGuid;
 } __attribute__((packed)) REFERRAL3;
 
 typedef struct smb_com_transaction_get_dfs_refer_rsp {
diff --git a/fs/cifs/cifssmb.c b/fs/cifs/cifssmb.c
index fc29738..6f8ed93 100644
--- a/fs/cifs/cifssmb.c
+++ b/fs/cifs/cifssmb.c
@@ -81,6 +81,39 @@
 #endif /* CONFIG_CIFS_WEAK_PW_HASH */
 #endif /* CIFS_POSIX */
 
+/* Allocates buffer into dst and copies smb string from src to it.
+ * caller is responsible for freeing dst if function returned 0.
+ * returns:
+ * 	on success - 0
+ *	on failure - errno
+ */
+static int
+cifs_strncpy_to_host(char **dst, const char *src, const int maxlen,
+		 const bool is_unicode, const struct nls_table *nls_codepage)
+{
+	int plen;
+
+	if (is_unicode) {
+		plen = UniStrnlen((wchar_t *)src, maxlen);
+		*dst = kmalloc(plen + 2, GFP_KERNEL);
+		if (!*dst)
+			goto cifs_strncpy_to_host_ErrExit;
+		cifs_strfromUCS_le(*dst, (__le16 *)src, plen, nls_codepage);
+	} else {
+		plen = strnlen(src, maxlen);
+		*dst = kmalloc(plen + 2, GFP_KERNEL);
+		if (!*dst)
+			goto cifs_strncpy_to_host_ErrExit;
+		strncpy(*dst, src, plen);
+	}
+	(*dst)[plen] = 0;
+	return 0;
+
+cifs_strncpy_to_host_ErrExit:
+	cERROR(1, ("Failed to allocate buffer for string\n"));
+	return -ENOMEM;
+}
+
 
 /* Mark as invalid, all open files on tree connections since they
    were closed when session to server was lost */
@@ -3867,6 +3900,96 @@
 	return rc;
 }
 
+/* parses DFS refferal V3 structure
+ * caller is responsible for freeing target_nodes
+ * returns:
+ * 	on success - 0
+ *	on failure - errno
+ */
+static int
+parse_DFS_REFERRALS(TRANSACTION2_GET_DFS_REFER_RSP *pSMBr,
+		unsigned int *num_of_nodes,
+		struct dfs_info3_param **target_nodes,
+		const struct nls_table *nls_codepage)
+{
+	int i, rc = 0;
+	char *data_end;
+	bool is_unicode;
+	struct dfs_referral_level_3 *ref;
+
+	is_unicode = pSMBr->hdr.Flags2 & SMBFLG2_UNICODE;
+	*num_of_nodes = le16_to_cpu(pSMBr->NumberOfReferrals);
+
+	if (*num_of_nodes < 1) {
+		cERROR(1, ("num_referrals: must be at least > 0,"
+			"but we get num_referrals = %d\n", *num_of_nodes));
+		rc = -EINVAL;
+		goto parse_DFS_REFERRALS_exit;
+	}
+
+	ref = (struct dfs_referral_level_3 *) &(pSMBr->referrals);
+	if (ref->VersionNumber != 3) {
+		cERROR(1, ("Referrals of V%d version are not supported,"
+			"should be V3", ref->VersionNumber));
+		rc = -EINVAL;
+		goto parse_DFS_REFERRALS_exit;
+	}
+
+	/* get the upper boundary of the resp buffer */
+	data_end = (char *)(&(pSMBr->PathConsumed)) +
+				le16_to_cpu(pSMBr->t2.DataCount);
+
+	cFYI(1, ("num_referrals: %d dfs flags: 0x%x ... \n",
+			*num_of_nodes,
+			le16_to_cpu(pSMBr->DFSFlags)));
+
+	*target_nodes = kzalloc(sizeof(struct dfs_info3_param) *
+			*num_of_nodes, GFP_KERNEL);
+	if (*target_nodes == NULL) {
+		cERROR(1, ("Failed to allocate buffer for target_nodes\n"));
+		rc = -ENOMEM;
+		goto parse_DFS_REFERRALS_exit;
+	}
+
+	/* collect neccessary data from referrals */
+	for (i = 0; i < *num_of_nodes; i++) {
+		char *temp;
+		int max_len;
+		struct dfs_info3_param *node = (*target_nodes)+i;
+
+		node->flags = le16_to_cpu(pSMBr->DFSFlags);
+		node->path_consumed = le16_to_cpu(pSMBr->PathConsumed);
+		node->server_type = le16_to_cpu(ref->ServerType);
+		node->ref_flag = le16_to_cpu(ref->ReferralEntryFlags);
+
+		/* copy DfsPath */
+		temp = (char *)ref + le16_to_cpu(ref->DfsPathOffset);
+		max_len = data_end - temp;
+		rc = cifs_strncpy_to_host(&(node->path_name), temp,
+					max_len, is_unicode, nls_codepage);
+		if (rc)
+			goto parse_DFS_REFERRALS_exit;
+
+		/* copy link target UNC */
+		temp = (char *)ref + le16_to_cpu(ref->NetworkAddressOffset);
+		max_len = data_end - temp;
+		rc = cifs_strncpy_to_host(&(node->node_name), temp,
+					max_len, is_unicode, nls_codepage);
+		if (rc)
+			goto parse_DFS_REFERRALS_exit;
+
+		ref += ref->Size;
+	}
+
+parse_DFS_REFERRALS_exit:
+	if (rc) {
+		free_dfs_info_array(*target_nodes, *num_of_nodes);
+		*target_nodes = NULL;
+		*num_of_nodes = 0;
+	}
+	return rc;
+}
+
 int
 CIFSGetDFSRefer(const int xid, struct cifsSesInfo *ses,
 		const unsigned char *searchName,
@@ -3877,12 +4000,9 @@
 /* TRANS2_GET_DFS_REFERRAL */
 	TRANSACTION2_GET_DFS_REFER_REQ *pSMB = NULL;
 	TRANSACTION2_GET_DFS_REFER_RSP *pSMBr = NULL;
-	struct dfs_referral_level_3 *referrals = NULL;
 	int rc = 0;
 	int bytes_returned;
 	int name_len;
-	unsigned int i;
-	char *temp;
 	__u16 params, byte_count;
 	*num_of_nodes = 0;
 	*target_nodes = NULL;
@@ -3960,80 +4080,19 @@
 	rc = validate_t2((struct smb_t2_rsp *)pSMBr);
 
 	/* BB Also check if enough total bytes returned? */
-	if (rc || (pSMBr->ByteCount < 17))
+	if (rc || (pSMBr->ByteCount < 17)) {
 		rc = -EIO;      /* bad smb */
-	else {
-		__u16 data_offset = le16_to_cpu(pSMBr->t2.DataOffset);
-		__u16 data_count = le16_to_cpu(pSMBr->t2.DataCount);
-
-		cFYI(1, ("Decoding GetDFSRefer response BCC: %d  Offset %d",
-			 pSMBr->ByteCount, data_offset));
-		referrals =
-		    (struct dfs_referral_level_3 *)
-				(8 /* sizeof start of data block */ +
-				data_offset +
-				(char *) &pSMBr->hdr.Protocol);
-		cFYI(1, ("num_referrals: %d dfs flags: 0x%x ... \n"
-			"for referral one refer size: 0x%x srv "
-			"type: 0x%x refer flags: 0x%x ttl: 0x%x",
-			le16_to_cpu(pSMBr->NumberOfReferrals),
-			le16_to_cpu(pSMBr->DFSFlags),
-			le16_to_cpu(referrals->ReferralSize),
-			le16_to_cpu(referrals->ServerType),
-			le16_to_cpu(referrals->ReferralFlags),
-			le16_to_cpu(referrals->TimeToLive)));
-		/* BB This field is actually two bytes in from start of
-		   data block so we could do safety check that DataBlock
-		   begins at address of pSMBr->NumberOfReferrals */
-		*num_of_nodes = le16_to_cpu(pSMBr->NumberOfReferrals);
-
-		/* BB Fix below so can return more than one referral */
-		if (*num_of_nodes > 1)
-			*num_of_nodes = 1;
-
-		/* get the length of the strings describing refs */
-		name_len = 0;
-		for (i = 0; i < *num_of_nodes; i++) {
-			/* make sure that DfsPathOffset not past end */
-			__u16 offset = le16_to_cpu(referrals->DfsPathOffset);
-			if (offset > data_count) {
-				/* if invalid referral, stop here and do
-				not try to copy any more */
-				*num_of_nodes = i;
-				break;
-			}
-			temp = ((char *)referrals) + offset;
-
-			if (pSMBr->hdr.Flags2 & SMBFLG2_UNICODE) {
-				name_len += UniStrnlen((wchar_t *)temp,
-							data_count);
-			} else {
-				name_len += strnlen(temp, data_count);
-			}
-			referrals++;
-			/* BB add check that referral pointer does
-			   not fall off end PDU */
-		}
-		/* BB add check for name_len bigger than bcc */
-		*target_nodes =
-			kmalloc(name_len+1+(*num_of_nodes),
-				GFP_KERNEL);
-		if (*target_nodes == NULL) {
-			rc = -ENOMEM;
-			goto GetDFSRefExit;
-		}
-
-		referrals = (struct dfs_referral_level_3 *)
-				(8 /* sizeof data hdr */ + data_offset +
-				(char *) &pSMBr->hdr.Protocol);
-
-		for (i = 0; i < *num_of_nodes; i++) {
-			temp = ((char *)referrals) +
-				  le16_to_cpu(referrals->DfsPathOffset);
-			/*  BB update target_uncs pointers */
-			referrals++;
-		}
+		goto GetDFSRefExit;
 	}
+
+	cFYI(1, ("Decoding GetDFSRefer response BCC: %d  Offset %d",
+				pSMBr->ByteCount,
+				le16_to_cpu(pSMBr->t2.DataOffset)));
+
+	/* parse returned result into more usable form */
+	rc = parse_DFS_REFERRALS(pSMBr, num_of_nodes,
+				 target_nodes, nls_codepage);
+
 GetDFSRefExit:
 	if (pSMB)
 		cifs_buf_release(pSMB);