diff mbox

[v3,1/7] CIFS: move DFS response parsing out of SMB1 code

Message ID 20170228184034.18771-2-aaptel@suse.com (mailing list archive)
State New, archived
Headers show

Commit Message

Aurélien Aptel Feb. 28, 2017, 6:40 p.m. UTC
since the DFS payload is not tied to the SMB version we can:
* isolate the DFS payload in its own struct, and include that struct in
  packet structs
* move the function that parses the response to misc.c and make it work
  on the new DFS payload struct (add payload size and utf16 flag as a
  result).

Signed-off-by: Aurelien Aptel <aaptel@suse.com>
---
 fs/cifs/cifspdu.h   |  16 ++++---
 fs/cifs/cifsproto.h |   5 +++
 fs/cifs/cifssmb.c   | 119 +++-------------------------------------------------
 fs/cifs/misc.c      | 105 ++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 125 insertions(+), 120 deletions(-)

Comments

Pavel Shilovsky March 1, 2017, 1:58 a.m. UTC | #1
2017-02-28 10:40 GMT-08:00 Aurelien Aptel <aaptel@suse.com>:
> since the DFS payload is not tied to the SMB version we can:
> * isolate the DFS payload in its own struct, and include that struct in
>   packet structs
> * move the function that parses the response to misc.c and make it work
>   on the new DFS payload struct (add payload size and utf16 flag as a
>   result).
>
> Signed-off-by: Aurelien Aptel <aaptel@suse.com>
> ---
>  fs/cifs/cifspdu.h   |  16 ++++---
>  fs/cifs/cifsproto.h |   5 +++
>  fs/cifs/cifssmb.c   | 119 +++-------------------------------------------------
>  fs/cifs/misc.c      | 105 ++++++++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 125 insertions(+), 120 deletions(-)
>
> diff --git a/fs/cifs/cifspdu.h b/fs/cifs/cifspdu.h
> index f5b8730..1ce733f 100644
> --- a/fs/cifs/cifspdu.h
> +++ b/fs/cifs/cifspdu.h
> @@ -2086,17 +2086,21 @@ typedef struct dfs_referral_level_3 { /* version 4 is same, + one flag bit */
>         __u8   ServiceSiteGuid[16];  /* MBZ, ignored */
>  } __attribute__((packed)) REFERRAL3;
>
> -typedef struct smb_com_transaction_get_dfs_refer_rsp {
> -       struct smb_hdr hdr;     /* wct = 10 */
> -       struct trans2_resp t2;
> -       __u16 ByteCount;
> -       __u8 Pad;
> +struct get_dfs_referral_rsp {
>         __le16 PathConsumed;
>         __le16 NumberOfReferrals;
>         __le32 DFSFlags;
>         REFERRAL3 referrals[1]; /* array of level 3 dfs_referral structures */
>         /* followed by the strings pointed to by the referral structures */
> -} __attribute__((packed)) TRANSACTION2_GET_DFS_REFER_RSP;
> +} __packed;
> +
> +typedef struct smb_com_transaction_get_dfs_refer_rsp {
> +       struct smb_hdr hdr;     /* wct = 10 */
> +       struct trans2_resp t2;
> +       __u16 ByteCount;
> +       __u8 Pad;
> +       struct get_dfs_referral_rsp dfs_data;
> +} __packed TRANSACTION2_GET_DFS_REFER_RSP;
>
>  /* DFS Flags */
>  #define DFSREF_REFERRAL_SERVER  0x00000001 /* all targets are DFS roots */
> diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h
> index 406d2c1..c097830 100644
> --- a/fs/cifs/cifsproto.h
> +++ b/fs/cifs/cifsproto.h
> @@ -284,6 +284,11 @@ extern int get_dfs_path(const unsigned int xid, struct cifs_ses *ses,
>                         const struct nls_table *nls_codepage,
>                         unsigned int *num_referrals,
>                         struct dfs_info3_param **referrals, int remap);
> +extern int parse_dfs_referrals(struct get_dfs_referral_rsp *rsp, u32 rsp_size,
> +                              unsigned int *num_of_nodes,
> +                              struct dfs_info3_param **target_nodes,
> +                              const struct nls_table *nls_codepage, int remap,
> +                              const char *searchName, bool is_unicode);
>  extern void reset_cifs_unix_caps(unsigned int xid, struct cifs_tcon *tcon,
>                                  struct cifs_sb_info *cifs_sb,
>                                  struct smb_vol *vol);
> diff --git a/fs/cifs/cifssmb.c b/fs/cifs/cifssmb.c
> index f5099fb..5005c79 100644
> --- a/fs/cifs/cifssmb.c
> +++ b/fs/cifs/cifssmb.c
> @@ -4786,117 +4786,6 @@ CIFSGetSrvInodeNumber(const unsigned int xid, struct cifs_tcon *tcon,
>         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 remap,
> -               const char *searchName)
> -{
> -       int i, rc = 0;
> -       char *data_end;
> -       bool is_unicode;
> -       struct dfs_referral_level_3 *ref;
> -
> -       if (pSMBr->hdr.Flags2 & SMBFLG2_UNICODE)
> -               is_unicode = true;
> -       else
> -               is_unicode = false;
> -       *num_of_nodes = le16_to_cpu(pSMBr->NumberOfReferrals);
> -
> -       if (*num_of_nodes < 1) {
> -               cifs_dbg(VFS, "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 != cpu_to_le16(3)) {
> -               cifs_dbg(VFS, "Referrals of V%d version are not supported, should be V3\n",
> -                        le16_to_cpu(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);
> -
> -       cifs_dbg(FYI, "num_referrals: %d dfs flags: 0x%x ...\n",
> -                *num_of_nodes, le32_to_cpu(pSMBr->DFSFlags));
> -
> -       *target_nodes = kcalloc(*num_of_nodes, sizeof(struct dfs_info3_param),
> -                               GFP_KERNEL);
> -       if (*target_nodes == NULL) {
> -               rc = -ENOMEM;
> -               goto parse_DFS_referrals_exit;
> -       }
> -
> -       /* collect necessary 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 = le32_to_cpu(pSMBr->DFSFlags);
> -               if (is_unicode) {
> -                       __le16 *tmp = kmalloc(strlen(searchName)*2 + 2,
> -                                               GFP_KERNEL);
> -                       if (tmp == NULL) {
> -                               rc = -ENOMEM;
> -                               goto parse_DFS_referrals_exit;
> -                       }
> -                       cifsConvertToUTF16((__le16 *) tmp, searchName,
> -                                          PATH_MAX, nls_codepage, remap);
> -                       node->path_consumed = cifs_utf16_bytes(tmp,
> -                                       le16_to_cpu(pSMBr->PathConsumed),
> -                                       nls_codepage);
> -                       kfree(tmp);
> -               } else
> -                       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;
> -               node->path_name = cifs_strndup_from_utf16(temp, max_len,
> -                                               is_unicode, nls_codepage);
> -               if (!node->path_name) {
> -                       rc = -ENOMEM;
> -                       goto parse_DFS_referrals_exit;
> -               }
> -
> -               /* copy link target UNC */
> -               temp = (char *)ref + le16_to_cpu(ref->NetworkAddressOffset);
> -               max_len = data_end - temp;
> -               node->node_name = cifs_strndup_from_utf16(temp, max_len,
> -                                               is_unicode, nls_codepage);
> -               if (!node->node_name) {
> -                       rc = -ENOMEM;
> -                       goto parse_DFS_referrals_exit;
> -               }
> -
> -               ref++;
> -       }
> -
> -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 unsigned int xid, struct cifs_ses *ses,
>                 const char *search_name, struct dfs_info3_param **target_nodes,
> @@ -4993,9 +4882,11 @@ CIFSGetDFSRefer(const unsigned int xid, struct cifs_ses *ses,
>                  get_bcc(&pSMBr->hdr), 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, remap,
> -                                search_name);
> +       rc = parse_dfs_referrals(&pSMBr->dfs_data,
> +                                le16_to_cpu(pSMBr->t2.DataCount),
> +                                num_of_nodes, target_nodes, nls_codepage,
> +                                remap, search_name,
> +                                pSMBr->hdr.Flags2 & SMBFLG2_UNICODE);
>
>  GetDFSRefExit:
>         cifs_buf_release(pSMB);
> diff --git a/fs/cifs/misc.c b/fs/cifs/misc.c
> index c672915..d3fb115 100644
> --- a/fs/cifs/misc.c
> +++ b/fs/cifs/misc.c
> @@ -640,3 +640,108 @@ cifs_add_pending_open(struct cifs_fid *fid, struct tcon_link *tlink,
>         cifs_add_pending_open_locked(fid, tlink, open);
>         spin_unlock(&tlink_tcon(open->tlink)->open_file_lock);
>  }
> +
> +/* parses DFS refferal V3 structure
> + * caller is responsible for freeing target_nodes
> + * returns:
> + * - on success - 0
> + * - on failure - errno
> + */
> +int
> +parse_dfs_referrals(struct get_dfs_referral_rsp *rsp, u32 rsp_size,
> +                   unsigned int *num_of_nodes,
> +                   struct dfs_info3_param **target_nodes,
> +                   const struct nls_table *nls_codepage, int remap,
> +                   const char *searchName, bool is_unicode)
> +{
> +       int i, rc = 0;
> +       char *data_end;
> +       struct dfs_referral_level_3 *ref;
> +
> +       *num_of_nodes = le16_to_cpu(rsp->NumberOfReferrals);
> +
> +       if (*num_of_nodes < 1) {
> +               cifs_dbg(VFS, "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 *) &(rsp->referrals);
> +       if (ref->VersionNumber != cpu_to_le16(3)) {
> +               cifs_dbg(VFS, "Referrals of V%d version are not supported, should be V3\n",
> +                        le16_to_cpu(ref->VersionNumber));
> +               rc = -EINVAL;
> +               goto parse_DFS_referrals_exit;
> +       }
> +
> +       /* get the upper boundary of the resp buffer */
> +       data_end = (char *)rsp + rsp_size;
> +
> +       cifs_dbg(FYI, "num_referrals: %d dfs flags: 0x%x ...\n",
> +                *num_of_nodes, le32_to_cpu(rsp->DFSFlags));
> +
> +       *target_nodes = kcalloc(*num_of_nodes, sizeof(struct dfs_info3_param),
> +                               GFP_KERNEL);
> +       if (*target_nodes == NULL) {
> +               rc = -ENOMEM;
> +               goto parse_DFS_referrals_exit;
> +       }
> +
> +       /* collect necessary 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 = le32_to_cpu(rsp->DFSFlags);
> +               if (is_unicode) {
> +                       __le16 *tmp = kmalloc(strlen(searchName)*2 + 2,
> +                                               GFP_KERNEL);
> +                       if (tmp == NULL) {
> +                               rc = -ENOMEM;
> +                               goto parse_DFS_referrals_exit;
> +                       }
> +                       cifsConvertToUTF16((__le16 *) tmp, searchName,
> +                                          PATH_MAX, nls_codepage, remap);
> +                       node->path_consumed = cifs_utf16_bytes(tmp,
> +                                       le16_to_cpu(rsp->PathConsumed),
> +                                       nls_codepage);
> +                       kfree(tmp);
> +               } else
> +                       node->path_consumed = le16_to_cpu(rsp->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;
> +               node->path_name = cifs_strndup_from_utf16(temp, max_len,
> +                                               is_unicode, nls_codepage);
> +               if (!node->path_name) {
> +                       rc = -ENOMEM;
> +                       goto parse_DFS_referrals_exit;
> +               }
> +
> +               /* copy link target UNC */
> +               temp = (char *)ref + le16_to_cpu(ref->NetworkAddressOffset);
> +               max_len = data_end - temp;
> +               node->node_name = cifs_strndup_from_utf16(temp, max_len,
> +                                               is_unicode, nls_codepage);
> +               if (!node->node_name) {
> +                       rc = -ENOMEM;
> +                       goto parse_DFS_referrals_exit;
> +               }
> +
> +               ref++;
> +       }
> +
> +parse_DFS_referrals_exit:
> +       if (rc) {
> +               free_dfs_info_array(*target_nodes, *num_of_nodes);
> +               *target_nodes = NULL;
> +               *num_of_nodes = 0;
> +       }
> +       return rc;
> +}
> --
> 2.10.2
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-cifs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

Acked-by: Pavel Shilovsky <pshilov@microsoft.com>
diff mbox

Patch

diff --git a/fs/cifs/cifspdu.h b/fs/cifs/cifspdu.h
index f5b8730..1ce733f 100644
--- a/fs/cifs/cifspdu.h
+++ b/fs/cifs/cifspdu.h
@@ -2086,17 +2086,21 @@  typedef struct dfs_referral_level_3 { /* version 4 is same, + one flag bit */
 	__u8   ServiceSiteGuid[16];  /* MBZ, ignored */
 } __attribute__((packed)) REFERRAL3;
 
-typedef struct smb_com_transaction_get_dfs_refer_rsp {
-	struct smb_hdr hdr;	/* wct = 10 */
-	struct trans2_resp t2;
-	__u16 ByteCount;
-	__u8 Pad;
+struct get_dfs_referral_rsp {
 	__le16 PathConsumed;
 	__le16 NumberOfReferrals;
 	__le32 DFSFlags;
 	REFERRAL3 referrals[1];	/* array of level 3 dfs_referral structures */
 	/* followed by the strings pointed to by the referral structures */
-} __attribute__((packed)) TRANSACTION2_GET_DFS_REFER_RSP;
+} __packed;
+
+typedef struct smb_com_transaction_get_dfs_refer_rsp {
+	struct smb_hdr hdr;	/* wct = 10 */
+	struct trans2_resp t2;
+	__u16 ByteCount;
+	__u8 Pad;
+	struct get_dfs_referral_rsp dfs_data;
+} __packed TRANSACTION2_GET_DFS_REFER_RSP;
 
 /* DFS Flags */
 #define DFSREF_REFERRAL_SERVER  0x00000001 /* all targets are DFS roots */
diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h
index 406d2c1..c097830 100644
--- a/fs/cifs/cifsproto.h
+++ b/fs/cifs/cifsproto.h
@@ -284,6 +284,11 @@  extern int get_dfs_path(const unsigned int xid, struct cifs_ses *ses,
 			const struct nls_table *nls_codepage,
 			unsigned int *num_referrals,
 			struct dfs_info3_param **referrals, int remap);
+extern int parse_dfs_referrals(struct get_dfs_referral_rsp *rsp, u32 rsp_size,
+			       unsigned int *num_of_nodes,
+			       struct dfs_info3_param **target_nodes,
+			       const struct nls_table *nls_codepage, int remap,
+			       const char *searchName, bool is_unicode);
 extern void reset_cifs_unix_caps(unsigned int xid, struct cifs_tcon *tcon,
 				 struct cifs_sb_info *cifs_sb,
 				 struct smb_vol *vol);
diff --git a/fs/cifs/cifssmb.c b/fs/cifs/cifssmb.c
index f5099fb..5005c79 100644
--- a/fs/cifs/cifssmb.c
+++ b/fs/cifs/cifssmb.c
@@ -4786,117 +4786,6 @@  CIFSGetSrvInodeNumber(const unsigned int xid, struct cifs_tcon *tcon,
 	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 remap,
-		const char *searchName)
-{
-	int i, rc = 0;
-	char *data_end;
-	bool is_unicode;
-	struct dfs_referral_level_3 *ref;
-
-	if (pSMBr->hdr.Flags2 & SMBFLG2_UNICODE)
-		is_unicode = true;
-	else
-		is_unicode = false;
-	*num_of_nodes = le16_to_cpu(pSMBr->NumberOfReferrals);
-
-	if (*num_of_nodes < 1) {
-		cifs_dbg(VFS, "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 != cpu_to_le16(3)) {
-		cifs_dbg(VFS, "Referrals of V%d version are not supported, should be V3\n",
-			 le16_to_cpu(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);
-
-	cifs_dbg(FYI, "num_referrals: %d dfs flags: 0x%x ...\n",
-		 *num_of_nodes, le32_to_cpu(pSMBr->DFSFlags));
-
-	*target_nodes = kcalloc(*num_of_nodes, sizeof(struct dfs_info3_param),
-				GFP_KERNEL);
-	if (*target_nodes == NULL) {
-		rc = -ENOMEM;
-		goto parse_DFS_referrals_exit;
-	}
-
-	/* collect necessary 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 = le32_to_cpu(pSMBr->DFSFlags);
-		if (is_unicode) {
-			__le16 *tmp = kmalloc(strlen(searchName)*2 + 2,
-						GFP_KERNEL);
-			if (tmp == NULL) {
-				rc = -ENOMEM;
-				goto parse_DFS_referrals_exit;
-			}
-			cifsConvertToUTF16((__le16 *) tmp, searchName,
-					   PATH_MAX, nls_codepage, remap);
-			node->path_consumed = cifs_utf16_bytes(tmp,
-					le16_to_cpu(pSMBr->PathConsumed),
-					nls_codepage);
-			kfree(tmp);
-		} else
-			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;
-		node->path_name = cifs_strndup_from_utf16(temp, max_len,
-						is_unicode, nls_codepage);
-		if (!node->path_name) {
-			rc = -ENOMEM;
-			goto parse_DFS_referrals_exit;
-		}
-
-		/* copy link target UNC */
-		temp = (char *)ref + le16_to_cpu(ref->NetworkAddressOffset);
-		max_len = data_end - temp;
-		node->node_name = cifs_strndup_from_utf16(temp, max_len,
-						is_unicode, nls_codepage);
-		if (!node->node_name) {
-			rc = -ENOMEM;
-			goto parse_DFS_referrals_exit;
-		}
-
-		ref++;
-	}
-
-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 unsigned int xid, struct cifs_ses *ses,
 		const char *search_name, struct dfs_info3_param **target_nodes,
@@ -4993,9 +4882,11 @@  CIFSGetDFSRefer(const unsigned int xid, struct cifs_ses *ses,
 		 get_bcc(&pSMBr->hdr), 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, remap,
-				 search_name);
+	rc = parse_dfs_referrals(&pSMBr->dfs_data,
+				 le16_to_cpu(pSMBr->t2.DataCount),
+				 num_of_nodes, target_nodes, nls_codepage,
+				 remap, search_name,
+				 pSMBr->hdr.Flags2 & SMBFLG2_UNICODE);
 
 GetDFSRefExit:
 	cifs_buf_release(pSMB);
diff --git a/fs/cifs/misc.c b/fs/cifs/misc.c
index c672915..d3fb115 100644
--- a/fs/cifs/misc.c
+++ b/fs/cifs/misc.c
@@ -640,3 +640,108 @@  cifs_add_pending_open(struct cifs_fid *fid, struct tcon_link *tlink,
 	cifs_add_pending_open_locked(fid, tlink, open);
 	spin_unlock(&tlink_tcon(open->tlink)->open_file_lock);
 }
+
+/* parses DFS refferal V3 structure
+ * caller is responsible for freeing target_nodes
+ * returns:
+ * - on success - 0
+ * - on failure - errno
+ */
+int
+parse_dfs_referrals(struct get_dfs_referral_rsp *rsp, u32 rsp_size,
+		    unsigned int *num_of_nodes,
+		    struct dfs_info3_param **target_nodes,
+		    const struct nls_table *nls_codepage, int remap,
+		    const char *searchName, bool is_unicode)
+{
+	int i, rc = 0;
+	char *data_end;
+	struct dfs_referral_level_3 *ref;
+
+	*num_of_nodes = le16_to_cpu(rsp->NumberOfReferrals);
+
+	if (*num_of_nodes < 1) {
+		cifs_dbg(VFS, "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 *) &(rsp->referrals);
+	if (ref->VersionNumber != cpu_to_le16(3)) {
+		cifs_dbg(VFS, "Referrals of V%d version are not supported, should be V3\n",
+			 le16_to_cpu(ref->VersionNumber));
+		rc = -EINVAL;
+		goto parse_DFS_referrals_exit;
+	}
+
+	/* get the upper boundary of the resp buffer */
+	data_end = (char *)rsp + rsp_size;
+
+	cifs_dbg(FYI, "num_referrals: %d dfs flags: 0x%x ...\n",
+		 *num_of_nodes, le32_to_cpu(rsp->DFSFlags));
+
+	*target_nodes = kcalloc(*num_of_nodes, sizeof(struct dfs_info3_param),
+				GFP_KERNEL);
+	if (*target_nodes == NULL) {
+		rc = -ENOMEM;
+		goto parse_DFS_referrals_exit;
+	}
+
+	/* collect necessary 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 = le32_to_cpu(rsp->DFSFlags);
+		if (is_unicode) {
+			__le16 *tmp = kmalloc(strlen(searchName)*2 + 2,
+						GFP_KERNEL);
+			if (tmp == NULL) {
+				rc = -ENOMEM;
+				goto parse_DFS_referrals_exit;
+			}
+			cifsConvertToUTF16((__le16 *) tmp, searchName,
+					   PATH_MAX, nls_codepage, remap);
+			node->path_consumed = cifs_utf16_bytes(tmp,
+					le16_to_cpu(rsp->PathConsumed),
+					nls_codepage);
+			kfree(tmp);
+		} else
+			node->path_consumed = le16_to_cpu(rsp->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;
+		node->path_name = cifs_strndup_from_utf16(temp, max_len,
+						is_unicode, nls_codepage);
+		if (!node->path_name) {
+			rc = -ENOMEM;
+			goto parse_DFS_referrals_exit;
+		}
+
+		/* copy link target UNC */
+		temp = (char *)ref + le16_to_cpu(ref->NetworkAddressOffset);
+		max_len = data_end - temp;
+		node->node_name = cifs_strndup_from_utf16(temp, max_len,
+						is_unicode, nls_codepage);
+		if (!node->node_name) {
+			rc = -ENOMEM;
+			goto parse_DFS_referrals_exit;
+		}
+
+		ref++;
+	}
+
+parse_DFS_referrals_exit:
+	if (rc) {
+		free_dfs_info_array(*target_nodes, *num_of_nodes);
+		*target_nodes = NULL;
+		*num_of_nodes = 0;
+	}
+	return rc;
+}