SMB3: Fix 3.11 encryption to Windows and handle encrypted smb3 tcon
[linux-2.6-block.git] / fs / cifs / smb2pdu.c
index f7741cee2a4cc73366dba1c724a8c74c38f11744..9aea138dd71fb1d57d89e5f9cd744f3460bf6f51 100644 (file)
@@ -268,8 +268,11 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon)
        mutex_unlock(&tcon->ses->session_mutex);
 
        cifs_dbg(FYI, "reconnect tcon rc = %d\n", rc);
-       if (rc)
+       if (rc) {
+               /* If sess reconnected but tcon didn't, something strange ... */
+               printk_once(KERN_WARNING "reconnect tcon failed rc = %d\n", rc);
                goto out;
+       }
 
        if (smb2_command != SMB2_INTERNAL_CMD)
                queue_delayed_work(cifsiod_wq, &server->reconnect, 0);
@@ -380,10 +383,10 @@ static void
 build_encrypt_ctxt(struct smb2_encryption_neg_context *pneg_ctxt)
 {
        pneg_ctxt->ContextType = SMB2_ENCRYPTION_CAPABILITIES;
-       pneg_ctxt->DataLength = cpu_to_le16(6);
-       pneg_ctxt->CipherCount = cpu_to_le16(2);
-       pneg_ctxt->Ciphers[0] = SMB2_ENCRYPTION_AES128_GCM;
-       pneg_ctxt->Ciphers[1] = SMB2_ENCRYPTION_AES128_CCM;
+       pneg_ctxt->DataLength = cpu_to_le16(4); /* Cipher Count + le16 cipher */
+       pneg_ctxt->CipherCount = cpu_to_le16(1);
+/* pneg_ctxt->Ciphers[0] = SMB2_ENCRYPTION_AES128_GCM;*/ /* not supported yet */
+       pneg_ctxt->Ciphers[0] = SMB2_ENCRYPTION_AES128_CCM;
 }
 
 static void
@@ -403,6 +406,101 @@ assemble_neg_contexts(struct smb2_negotiate_req *req,
        *total_len += 4 + sizeof(struct smb2_preauth_neg_context)
                + sizeof(struct smb2_encryption_neg_context);
 }
+
+static void decode_preauth_context(struct smb2_preauth_neg_context *ctxt)
+{
+       unsigned int len = le16_to_cpu(ctxt->DataLength);
+
+       /* If invalid preauth context warn but use what we requested, SHA-512 */
+       if (len < MIN_PREAUTH_CTXT_DATA_LEN) {
+               printk_once(KERN_WARNING "server sent bad preauth context\n");
+               return;
+       }
+       if (le16_to_cpu(ctxt->HashAlgorithmCount) != 1)
+               printk_once(KERN_WARNING "illegal SMB3 hash algorithm count\n");
+       if (ctxt->HashAlgorithms != SMB2_PREAUTH_INTEGRITY_SHA512)
+               printk_once(KERN_WARNING "unknown SMB3 hash algorithm\n");
+}
+
+static int decode_encrypt_ctx(struct TCP_Server_Info *server,
+                             struct smb2_encryption_neg_context *ctxt)
+{
+       unsigned int len = le16_to_cpu(ctxt->DataLength);
+
+       cifs_dbg(FYI, "decode SMB3.11 encryption neg context of len %d\n", len);
+       if (len < MIN_ENCRYPT_CTXT_DATA_LEN) {
+               printk_once(KERN_WARNING "server sent bad crypto ctxt len\n");
+               return -EINVAL;
+       }
+
+       if (le16_to_cpu(ctxt->CipherCount) != 1) {
+               printk_once(KERN_WARNING "illegal SMB3.11 cipher count\n");
+               return -EINVAL;
+       }
+       cifs_dbg(FYI, "SMB311 cipher type:%d\n", le16_to_cpu(ctxt->Ciphers[0]));
+       if ((ctxt->Ciphers[0] != SMB2_ENCRYPTION_AES128_CCM) &&
+           (ctxt->Ciphers[0] != SMB2_ENCRYPTION_AES128_GCM)) {
+               printk_once(KERN_WARNING "invalid SMB3.11 cipher returned\n");
+               return -EINVAL;
+       }
+       server->cipher_type = ctxt->Ciphers[0];
+       server->capabilities |= SMB2_GLOBAL_CAP_ENCRYPTION;
+       return 0;
+}
+
+static int smb311_decode_neg_context(struct smb2_negotiate_rsp *rsp,
+                                    struct TCP_Server_Info *server)
+{
+       struct smb2_neg_context *pctx;
+       unsigned int offset = le32_to_cpu(rsp->NegotiateContextOffset);
+       unsigned int ctxt_cnt = le16_to_cpu(rsp->NegotiateContextCount);
+       unsigned int len_of_smb = be32_to_cpu(rsp->hdr.smb2_buf_length);
+       unsigned int len_of_ctxts, i;
+       int rc = 0;
+
+       cifs_dbg(FYI, "decoding %d negotiate contexts\n", ctxt_cnt);
+       if (len_of_smb <= offset) {
+               cifs_dbg(VFS, "Invalid response: negotiate context offset\n");
+               return -EINVAL;
+       }
+
+       len_of_ctxts = len_of_smb - offset;
+
+       for (i = 0; i < ctxt_cnt; i++) {
+               int clen;
+               /* check that offset is not beyond end of SMB */
+               if (len_of_ctxts == 0)
+                       break;
+
+               if (len_of_ctxts < sizeof(struct smb2_neg_context))
+                       break;
+
+               pctx = (struct smb2_neg_context *)(offset +
+                       server->vals->header_preamble_size + (char *)rsp);
+               clen = le16_to_cpu(pctx->DataLength);
+               if (clen > len_of_ctxts)
+                       break;
+
+               if (pctx->ContextType == SMB2_PREAUTH_INTEGRITY_CAPABILITIES)
+                       decode_preauth_context(
+                               (struct smb2_preauth_neg_context *)pctx);
+               else if (pctx->ContextType == SMB2_ENCRYPTION_CAPABILITIES)
+                       rc = decode_encrypt_ctx(server,
+                               (struct smb2_encryption_neg_context *)pctx);
+               else
+                       cifs_dbg(VFS, "unknown negcontext of type %d ignored\n",
+                               le16_to_cpu(pctx->ContextType));
+
+               if (rc)
+                       break;
+               /* offsets must be 8 byte aligned */
+               clen = (clen + 7) & ~0x7;
+               offset += clen + sizeof(struct smb2_neg_context);
+               len_of_ctxts -= clen;
+       }
+       return rc;
+}
+
 #else
 static void assemble_neg_contexts(struct smb2_negotiate_req *req,
                                  unsigned int *total_len)
@@ -616,6 +714,15 @@ SMB2_negotiate(const unsigned int xid, struct cifs_ses *ses)
                else if (rc == 0)
                        rc = -EIO;
        }
+
+#ifdef CONFIG_CIFS_SMB311
+       if (rsp->DialectRevision == cpu_to_le16(SMB311_PROT_ID)) {
+               if (rsp->NegotiateContextCount)
+                       rc = smb311_decode_neg_context(rsp, server);
+               else
+                       cifs_dbg(VFS, "Missing expected negotiate contexts\n");
+       }
+#endif /* CONFIG_CIFS_SMB311 */
 neg_exit:
        free_rsp_buf(resp_buftype, rsp);
        return rc;
@@ -1026,7 +1133,7 @@ SMB2_sess_auth_rawntlmssp_negotiate(struct SMB2_sess_data *sess_data)
        if (rc)
                goto out;
 
-       if (offsetof(struct smb2_sess_setup_rsp, Buffer) - 4 !=
+       if (offsetof(struct smb2_sess_setup_rsp, Buffer) - ses->server->vals->header_preamble_size !=
                        le16_to_cpu(rsp->SecurityBufferOffset)) {
                cifs_dbg(VFS, "Invalid security buffer offset %d\n",
                        le16_to_cpu(rsp->SecurityBufferOffset));
@@ -1701,7 +1808,7 @@ alloc_path_with_tree_prefix(__le16 **out_path, int *out_size, int *out_len,
 int
 SMB2_open(const unsigned int xid, struct cifs_open_parms *oparms, __le16 *path,
          __u8 *oplock, struct smb2_file_all_info *buf,
-         struct smb2_err_rsp **err_buf)
+         struct kvec *err_iov)
 {
        struct smb2_create_req *req;
        struct smb2_create_rsp *rsp;
@@ -1841,9 +1948,11 @@ SMB2_open(const unsigned int xid, struct cifs_open_parms *oparms, __le16 *path,
 
        if (rc != 0) {
                cifs_stats_fail_inc(tcon, SMB2_CREATE_HE);
-               if (err_buf && rsp)
-                       *err_buf = kmemdup(rsp, get_rfc1002_length(rsp) + 4,
-                                          GFP_KERNEL);
+               if (err_iov && rsp) {
+                       *err_iov = rsp_iov;
+                       resp_buftype = CIFS_NO_BUFFER;
+                       rsp = NULL;
+               }
                goto creat_exit;
        }
 
@@ -2098,13 +2207,13 @@ close_exit:
 }
 
 static int
-validate_buf(unsigned int offset, unsigned int buffer_length,
-            struct smb2_hdr *hdr, unsigned int min_buf_size)
-
+validate_iov(struct TCP_Server_Info *server,
+            unsigned int offset, unsigned int buffer_length,
+            struct kvec *iov, unsigned int min_buf_size)
 {
-       unsigned int smb_len = be32_to_cpu(hdr->smb2_buf_length);
-       char *end_of_smb = smb_len + 4 /* RFC1001 length field */ + (char *)hdr;
-       char *begin_of_buf = 4 /* RFC1001 len field */ + offset + (char *)hdr;
+       unsigned int smb_len = iov->iov_len;
+       char *end_of_smb = smb_len + server->vals->header_preamble_size + (char *)iov->iov_base;
+       char *begin_of_buf = server->vals->header_preamble_size + offset + (char *)iov->iov_base;
        char *end_of_buf = begin_of_buf + buffer_length;
 
 
@@ -2134,18 +2243,18 @@ validate_buf(unsigned int offset, unsigned int buffer_length,
  * Caller must free buffer.
  */
 static int
-validate_and_copy_buf(unsigned int offset, unsigned int buffer_length,
-                     struct smb2_hdr *hdr, unsigned int minbufsize,
+validate_and_copy_iov(struct TCP_Server_Info *server,
+                     unsigned int offset, unsigned int buffer_length,
+                     struct kvec *iov, unsigned int minbufsize,
                      char *data)
-
 {
-       char *begin_of_buf = 4 /* RFC1001 len field */ + offset + (char *)hdr;
+       char *begin_of_buf = server->vals->header_preamble_size + offset + (char *)(iov->iov_base);
        int rc;
 
        if (!data)
                return -EINVAL;
 
-       rc = validate_buf(offset, buffer_length, hdr, minbufsize);
+       rc = validate_iov(server, offset, buffer_length, iov, minbufsize);
        if (rc)
                return rc;
 
@@ -2223,9 +2332,10 @@ query_info(const unsigned int xid, struct cifs_tcon *tcon,
                }
        }
 
-       rc = validate_and_copy_buf(le16_to_cpu(rsp->OutputBufferOffset),
+       rc = validate_and_copy_iov(ses->server,
+                                  le16_to_cpu(rsp->OutputBufferOffset),
                                   le32_to_cpu(rsp->OutputBufferLength),
-                                  &rsp->hdr, min_len, *data);
+                                  &rsp_iov, min_len, *data);
 
 qinf_exit:
        free_rsp_buf(resp_buftype, rsp);
@@ -3146,8 +3256,9 @@ SMB2_query_directory(const unsigned int xid, struct cifs_tcon *tcon,
                goto qdir_exit;
        }
 
-       rc = validate_buf(le16_to_cpu(rsp->OutputBufferOffset),
-                         le32_to_cpu(rsp->OutputBufferLength), &rsp->hdr,
+       rc = validate_iov(server,
+                         le16_to_cpu(rsp->OutputBufferOffset),
+                         le32_to_cpu(rsp->OutputBufferLength), &rsp_iov,
                          info_buf_size);
        if (rc)
                goto qdir_exit;
@@ -3454,7 +3565,7 @@ static int
 build_qfs_info_req(struct kvec *iov, struct cifs_tcon *tcon, int level,
                   int outbuf_len, u64 persistent_fid, u64 volatile_fid)
 {
-       struct TCP_Server_Info *server = tcon->ses->server;
+       struct TCP_Server_Info *server;
        int rc;
        struct smb2_query_info_req *req;
        unsigned int total_len;
@@ -3464,6 +3575,8 @@ build_qfs_info_req(struct kvec *iov, struct cifs_tcon *tcon, int level,
        if ((tcon->ses == NULL) || (tcon->ses->server == NULL))
                return -EIO;
 
+       server = tcon->ses->server;
+
        rc = smb2_plain_req_init(SMB2_QUERY_INFO, tcon, (void **) &req,
                             &total_len);
        if (rc)
@@ -3517,8 +3630,9 @@ SMB2_QFS_info(const unsigned int xid, struct cifs_tcon *tcon,
 
        info = (struct smb2_fs_full_size_info *)(server->vals->header_preamble_size +
                le16_to_cpu(rsp->OutputBufferOffset) + (char *)&rsp->hdr);
-       rc = validate_buf(le16_to_cpu(rsp->OutputBufferOffset),
-                         le32_to_cpu(rsp->OutputBufferLength), &rsp->hdr,
+       rc = validate_iov(server,
+                         le16_to_cpu(rsp->OutputBufferOffset),
+                         le32_to_cpu(rsp->OutputBufferLength), &rsp_iov,
                          sizeof(struct smb2_fs_full_size_info));
        if (!rc)
                copy_fs_info_to_kstatfs(info, fsdata);
@@ -3574,7 +3688,7 @@ SMB2_QFS_attr(const unsigned int xid, struct cifs_tcon *tcon,
 
        rsp_len = le32_to_cpu(rsp->OutputBufferLength);
        offset = le16_to_cpu(rsp->OutputBufferOffset);
-       rc = validate_buf(offset, rsp_len, &rsp->hdr, min_len);
+       rc = validate_iov(server, offset, rsp_len, &rsp_iov, min_len);
        if (rc)
                goto qfsattr_exit;