cifs: Add support for mounting Windows 2008 DFS shares
[linux-block.git] / fs / cifs / connect.c
index 277262a8e82f282d4a32b0ddd4a244bc55e99472..c7ebeee38c671b13d70aa4d6e8142619ccd2f097 100644 (file)
@@ -102,6 +102,7 @@ struct smb_vol {
        bool fsc:1;     /* enable fscache */
        bool mfsymlinks:1; /* use Minshall+French Symlinks */
        bool multiuser:1;
+       bool use_smb2:1; /* force smb2 use on mount instead of cifs */
        unsigned int rsize;
        unsigned int wsize;
        bool sockopt_tcp_nodelay:1;
@@ -316,19 +317,19 @@ static int coalesce_t2(struct smb_hdr *psecond, struct smb_hdr *pTargetSMB)
        put_unaligned_le16(total_in_buf, &pSMBt->t2_rsp.DataCount);
 
        /* fix up the BCC */
-       byte_count = get_bcc_le(pTargetSMB);
+       byte_count = get_bcc(pTargetSMB);
        byte_count += total_in_buf2;
        /* is the result too big for the field? */
        if (byte_count > USHRT_MAX)
                return -EPROTO;
-       put_bcc_le(byte_count, pTargetSMB);
+       put_bcc(byte_count, pTargetSMB);
 
-       byte_count = pTargetSMB->smb_buf_length;
+       byte_count = be32_to_cpu(pTargetSMB->smb_buf_length);
        byte_count += total_in_buf2;
        /* don't allow buffer to overflow */
        if (byte_count > CIFSMaxBufSize)
                return -ENOBUFS;
-       pTargetSMB->smb_buf_length = byte_count;
+       pTargetSMB->smb_buf_length = cpu_to_be32(byte_count);
 
        memcpy(data_area_of_target, data_area_of_buf2, total_in_buf2);
 
@@ -495,8 +496,7 @@ incomplete_rcv:
                /* Note that FC 1001 length is big endian on the wire,
                but we convert it here so it is always manipulated
                as host byte order */
-               pdu_length = be32_to_cpu((__force __be32)smb_buffer->smb_buf_length);
-               smb_buffer->smb_buf_length = pdu_length;
+               pdu_length = be32_to_cpu(smb_buffer->smb_buf_length);
 
                cFYI(1, "rfc1002 length 0x%x", pdu_length+4);
 
@@ -735,7 +735,7 @@ multi_t2_fnd:
                sock_release(csocket);
                server->ssocket = NULL;
        }
-       /* buffer usuallly freed in free_mid - need to free it here on exit */
+       /* buffer usually freed in free_mid - need to free it here on exit */
        cifs_buf_release(bigbuf);
        if (smallbuf) /* no sense logging a debug message if NULL */
                cifs_small_buf_release(smallbuf);
@@ -1038,6 +1038,22 @@ cifs_parse_mount_options(char *options, const char *devname,
                                cERROR(1, "bad security option: %s", value);
                                return 1;
                        }
+               } else if (strnicmp(data, "vers", 3) == 0) {
+                       if (!value || !*value) {
+                               cERROR(1, "no protocol version specified"
+                                         " after vers= mount option");
+                       } else if ((strnicmp(value, "cifs", 4) == 0) ||
+                                  (strnicmp(value, "1", 1) == 0)) {
+                               /* this is the default */
+                               continue;
+                       } else if ((strnicmp(value, "smb2", 4) == 0) ||
+                                  (strnicmp(value, "2", 1) == 0)) {
+#ifdef CONFIG_CIFS_SMB2
+                               vol->use_smb2 = true;
+#else
+                               cERROR(1, "smb2 support not enabled");
+#endif /* CONFIG_CIFS_SMB2 */
+                       }
                } else if ((strnicmp(data, "unc", 3) == 0)
                           || (strnicmp(data, "target", 6) == 0)
                           || (strnicmp(data, "path", 4) == 0)) {
@@ -2280,7 +2296,7 @@ ip_rfc1001_connect(struct TCP_Server_Info *server)
                smb_buf = (struct smb_hdr *)ses_init_buf;
 
                /* sizeof RFC1002_SESSION_REQUEST with no scope */
-               smb_buf->smb_buf_length = 0x81000044;
+               smb_buf->smb_buf_length = cpu_to_be32(0x81000044);
                rc = smb_send(server, smb_buf, 0x44);
                kfree(ses_init_buf);
                /*
@@ -2729,6 +2745,57 @@ build_unc_path_to_root(const struct smb_vol *volume_info,
        full_path[unc_len + cifs_sb->prepathlen] = 0; /* add trailing null */
        return full_path;
 }
+
+/*
+ * Perform a dfs referral query for a share and (optionally) prefix
+ *
+ * If a referral is found, mount_data will be set to point at a newly
+ * allocated string containing updated options for the submount.
+ * Otherwise it will be left untouched.
+ *
+ * Returns the rc from get_dfs_path to the caller, which can be used to
+ * determine whether there were referrals.
+ */
+static int
+expand_dfs_referral(int xid, struct cifsSesInfo *pSesInfo,
+                   struct smb_vol *volume_info, struct cifs_sb_info *cifs_sb,
+                   char **mount_data, int check_prefix)
+{
+       int rc;
+       unsigned int num_referrals = 0;
+       struct dfs_info3_param *referrals = NULL;
+       char *full_path = NULL, *ref_path = NULL, *mdata = NULL;
+
+       full_path = build_unc_path_to_root(volume_info, cifs_sb);
+       if (IS_ERR(full_path))
+               return PTR_ERR(full_path);
+
+       /* For DFS paths, skip the first '\' of the UNC */
+       ref_path = check_prefix ? full_path + 1 : volume_info->UNC + 1;
+
+       rc = get_dfs_path(xid, pSesInfo , ref_path, cifs_sb->local_nls,
+                         &num_referrals, &referrals,
+                         cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MAP_SPECIAL_CHR);
+
+       if (!rc && num_referrals > 0) {
+               char *fake_devname = NULL;
+
+               mdata = cifs_compose_mount_options(cifs_sb->mountdata,
+                                                  full_path + 1, referrals,
+                                                  &fake_devname);
+
+               free_dfs_info_array(referrals, num_referrals);
+               kfree(fake_devname);
+
+               if (IS_ERR(mdata)) {
+                       rc = PTR_ERR(mdata);
+                       mdata = NULL;
+               }
+               *mount_data = mdata;
+       }
+       kfree(full_path);
+       return rc;
+}
 #endif
 
 int
@@ -2745,10 +2812,19 @@ cifs_mount(struct super_block *sb, struct cifs_sb_info *cifs_sb,
        char *mount_data = mount_data_global;
        struct tcon_link *tlink;
 #ifdef CONFIG_CIFS_DFS_UPCALL
-       struct dfs_info3_param *referrals = NULL;
-       unsigned int num_referrals = 0;
        int referral_walks_count = 0;
 try_mount_again:
+
+       /* cleanup activities if we're chasing a referral */
+       if (referral_walks_count) {
+               if (tcon)
+                       cifs_put_tcon(tcon);
+               else if (pSesInfo)
+                       cifs_put_smb_ses(pSesInfo);
+
+               cleanup_volume_info(&volume_info);
+               FreeXid(xid);
+       }
 #endif
        rc = 0;
        tcon = NULL;
@@ -2861,6 +2937,24 @@ try_mount_again:
                               (tcon->ses->server->maxBuf - MAX_CIFS_HDR_SIZE));
 
 remote_path_check:
+#ifdef CONFIG_CIFS_DFS_UPCALL
+       /*
+        * Perform an unconditional check for whether there are DFS
+        * referrals for this path without prefix, to provide support
+        * for DFS referrals from w2k8 servers which don't seem to respond
+        * with PATH_NOT_COVERED to requests that include the prefix.
+        * Chase the referral if found, otherwise continue normally.
+        */
+       if (referral_walks_count == 0) {
+               int refrc = expand_dfs_referral(xid, pSesInfo, volume_info,
+                                               cifs_sb, &mount_data, false);
+               if (!refrc) {
+                       referral_walks_count++;
+                       goto try_mount_again;
+               }
+       }
+#endif
+
        /* check if a whole path (including prepath) is not remote */
        if (!rc && tcon) {
                /* build_path_to_root works only when we have a valid tcon */
@@ -2894,46 +2988,19 @@ remote_path_check:
                if ((cifs_sb->mnt_cifs_flags & CIFS_MOUNT_POSIX_PATHS) == 0)
                        convert_delimiter(cifs_sb->prepath,
                                        CIFS_DIR_SEP(cifs_sb));
-               full_path = build_unc_path_to_root(volume_info, cifs_sb);
-               if (IS_ERR(full_path)) {
-                       rc = PTR_ERR(full_path);
-                       goto mount_fail_check;
-               }
-
-               cFYI(1, "Getting referral for: %s", full_path);
-               rc = get_dfs_path(xid, pSesInfo , full_path + 1,
-                       cifs_sb->local_nls, &num_referrals, &referrals,
-                       cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MAP_SPECIAL_CHR);
-               if (!rc && num_referrals > 0) {
-                       char *fake_devname = NULL;
-
-                       if (mount_data != mount_data_global)
-                               kfree(mount_data);
-
-                       mount_data = cifs_compose_mount_options(
-                                       cifs_sb->mountdata, full_path + 1,
-                                       referrals, &fake_devname);
-
-                       free_dfs_info_array(referrals, num_referrals);
-                       kfree(fake_devname);
-                       kfree(full_path);
 
-                       if (IS_ERR(mount_data)) {
-                               rc = PTR_ERR(mount_data);
-                               mount_data = NULL;
-                               goto mount_fail_check;
-                       }
+               if (mount_data != mount_data_global)
+                       kfree(mount_data);
 
-                       if (tcon)
-                               cifs_put_tcon(tcon);
-                       else if (pSesInfo)
-                               cifs_put_smb_ses(pSesInfo);
+               rc = expand_dfs_referral(xid, pSesInfo, volume_info, cifs_sb,
+                                        &mount_data, true);
 
-                       cleanup_volume_info(&volume_info);
+               if (!rc) {
                        referral_walks_count++;
-                       FreeXid(xid);
                        goto try_mount_again;
                }
+               mount_data = NULL;
+               goto mount_fail_check;
 #else /* No DFS support, return error on mount */
                rc = -EOPNOTSUPP;
 #endif
@@ -3083,7 +3150,8 @@ CIFSTCon(unsigned int xid, struct cifsSesInfo *ses,
        bcc_ptr += strlen("?????");
        bcc_ptr += 1;
        count = bcc_ptr - &pSMB->Password[0];
-       pSMB->hdr.smb_buf_length += count;
+       pSMB->hdr.smb_buf_length = cpu_to_be32(be32_to_cpu(
+                                       pSMB->hdr.smb_buf_length) + count);
        pSMB->ByteCount = cpu_to_le16(count);
 
        rc = SendReceive(xid, ses, smb_buffer, smb_buffer_response, &length,
@@ -3258,7 +3326,9 @@ cifs_construct_tcon(struct cifs_sb_info *cifs_sb, uid_t fsuid)
        struct cifsSesInfo *ses;
        struct cifsTconInfo *tcon = NULL;
        struct smb_vol *vol_info;
-       char username[MAX_USERNAME_SIZE + 1];
+       char username[28]; /* big enough for "krb50x" + hex of ULONG_MAX 6+16 */
+                          /* We used to have this as MAX_USERNAME which is   */
+                          /* way too big now (256 instead of 32) */
 
        vol_info = kzalloc(sizeof(*vol_info), GFP_KERNEL);
        if (vol_info == NULL) {