cifs: Separate rawntlmssp auth from CIFS_SessSetup()
authorSachin Prabhu <sprabhu@redhat.com>
Mon, 16 Jun 2014 14:35:28 +0000 (15:35 +0100)
committerSteve French <smfrench@gmail.com>
Fri, 1 Aug 2014 04:11:15 +0000 (23:11 -0500)
Separate rawntlmssp authentication from CIFS_SessSetup(). Also cleanup
CIFS_SessSetup() since we no longer do any auth within it.

Signed-off-by: Sachin Prabhu <sprabhu@redhat.com>
Reviewed-by: Shirish Pargaonkar <spargaonkar@suse.com>
Signed-off-by: Steve French <smfrench@gmail.com>
fs/cifs/sess.c

index b413fae..c2e7c07 100644 (file)
@@ -1113,241 +1113,225 @@ sess_auth_kerberos(struct sess_data *sess_data)
 }
 #endif /* ! CONFIG_CIFS_UPCALL */
 
-int
-CIFS_SessSetup(const unsigned int xid, struct cifs_ses *ses,
-              const struct nls_table *nls_cp)
+/*
+ * The required kvec buffers have to be allocated before calling this
+ * function.
+ */
+static int
+_sess_auth_rawntlmssp_assemble_req(struct sess_data *sess_data)
 {
-       int rc = 0;
-       int wct;
        struct smb_hdr *smb_buf;
-       char *bcc_ptr;
-       char *str_area;
        SESSION_SETUP_ANDX *pSMB;
+       struct cifs_ses *ses = sess_data->ses;
        __u32 capabilities;
-       __u16 count;
-       int resp_buf_type;
-       struct kvec iov[3];
-       enum securityEnum type;
-       __u16 action, bytes_remaining;
-       __le32 phase = NtLmNegotiate; /* NTLMSSP, if needed, is multistage */
-       u16 blob_len;
-       char *ntlmsspblob = NULL;
-       struct sess_data *sess_data;
+       char *bcc_ptr;
 
-       if (ses == NULL) {
-               WARN(1, "%s: ses == NULL!", __func__);
-               return -EINVAL;
+       pSMB = (SESSION_SETUP_ANDX *)sess_data->iov[0].iov_base;
+       smb_buf = (struct smb_hdr *)pSMB;
+
+       capabilities = cifs_ssetup_hdr(ses, pSMB);
+       if ((pSMB->req.hdr.Flags2 & SMBFLG2_UNICODE) == 0) {
+               cifs_dbg(VFS, "NTLMSSP requires Unicode support\n");
+               return -ENOSYS;
        }
 
-       sess_data = kzalloc(sizeof(struct sess_data), GFP_KERNEL);
-       if (!sess_data)
-               return -ENOMEM;
-       sess_data->xid = xid;
-       sess_data->ses = ses;
-       sess_data->buf0_type = CIFS_NO_BUFFER;
-       sess_data->nls_cp = (struct nls_table *) nls_cp;
+       pSMB->req.hdr.Flags2 |= SMBFLG2_EXT_SEC;
+       capabilities |= CAP_EXTENDED_SECURITY;
+       pSMB->req.Capabilities |= cpu_to_le32(capabilities);
 
-       type = select_sectype(ses->server, ses->sectype);
-       cifs_dbg(FYI, "sess setup type %d\n", type);
-       if (type == Unspecified) {
-               cifs_dbg(VFS,
-                       "Unable to select appropriate authentication method!");
-               return -EINVAL;
+       bcc_ptr = sess_data->iov[2].iov_base;
+       /* unicode strings must be word aligned */
+       if ((sess_data->iov[0].iov_len + sess_data->iov[1].iov_len) % 2) {
+               *bcc_ptr = 0;
+               bcc_ptr++;
        }
+       unicode_oslm_strings(&bcc_ptr, sess_data->nls_cp);
 
-       switch (type) {
-       case LANMAN:
-               sess_auth_lanman(sess_data);
-               goto out;
-       case NTLM:
-               sess_auth_ntlm(sess_data);
-               goto out;
-       case NTLMv2:
-               sess_auth_ntlmv2(sess_data);
-               goto out;
-       case Kerberos:
-               sess_auth_kerberos(sess_data);
-               goto out;
-       default:
-               cifs_dbg(FYI, "Continuing with CIFS_SessSetup\n");
-       }
+       sess_data->iov[2].iov_len = (long) bcc_ptr -
+                                       (long) sess_data->iov[2].iov_base;
 
-       if (type == RawNTLMSSP) {
-               /* if memory allocation is successful, caller of this function
-                * frees it.
-                */
-               ses->ntlmssp = kmalloc(sizeof(struct ntlmssp_auth), GFP_KERNEL);
-               if (!ses->ntlmssp)
-                       return -ENOMEM;
-               ses->ntlmssp->sesskey_per_smbsess = false;
+       return 0;
+}
+
+static void
+sess_auth_rawntlmssp_authenticate(struct sess_data *sess_data);
+
+static void
+sess_auth_rawntlmssp_negotiate(struct sess_data *sess_data)
+{
+       int rc;
+       struct smb_hdr *smb_buf;
+       SESSION_SETUP_ANDX *pSMB;
+       struct cifs_ses *ses = sess_data->ses;
+       __u16 bytes_remaining;
+       char *bcc_ptr;
+       u16 blob_len;
+
+       cifs_dbg(FYI, "rawntlmssp session setup negotiate phase\n");
 
+       /*
+        * if memory allocation is successful, caller of this function
+        * frees it.
+        */
+       ses->ntlmssp = kmalloc(sizeof(struct ntlmssp_auth), GFP_KERNEL);
+       if (!ses->ntlmssp) {
+               rc = -ENOMEM;
+               goto out;
        }
+       ses->ntlmssp->sesskey_per_smbsess = false;
 
-ssetup_ntlmssp_authenticate:
-       if (phase == NtLmChallenge)
-               phase = NtLmAuthenticate; /* if ntlmssp, now final phase */
+       /* wct = 12 */
+       rc = sess_alloc_buffer(sess_data, 12);
+       if (rc)
+               goto out;
 
-       /* same size: negotiate or auth, NTLMSSP or extended security */
-       wct = 12;
+       pSMB = (SESSION_SETUP_ANDX *)sess_data->iov[0].iov_base;
 
-       rc = small_smb_init_no_tc(SMB_COM_SESSION_SETUP_ANDX, wct, ses,
-                           (void **)&smb_buf);
+       /* Build security blob before we assemble the request */
+       build_ntlmssp_negotiate_blob(pSMB->req.SecurityBlob, ses);
+       sess_data->iov[1].iov_len = sizeof(NEGOTIATE_MESSAGE);
+       sess_data->iov[1].iov_base = pSMB->req.SecurityBlob;
+       pSMB->req.SecurityBlobLength = cpu_to_le16(sizeof(NEGOTIATE_MESSAGE));
+
+       rc = _sess_auth_rawntlmssp_assemble_req(sess_data);
        if (rc)
-               return rc;
+               goto out;
 
-       pSMB = (SESSION_SETUP_ANDX *)smb_buf;
+       rc = sess_sendreceive(sess_data);
 
-       capabilities = cifs_ssetup_hdr(ses, pSMB);
+       pSMB = (SESSION_SETUP_ANDX *)sess_data->iov[0].iov_base;
+       smb_buf = (struct smb_hdr *)sess_data->iov[0].iov_base;
 
-       /* we will send the SMB in three pieces:
-       a fixed length beginning part, an optional
-       SPNEGO blob (which can be zero length), and a
-       last part which will include the strings
-       and rest of bcc area. This allows us to avoid
-       a large buffer 17K allocation */
-       iov[0].iov_base = (char *)pSMB;
-       iov[0].iov_len = be32_to_cpu(smb_buf->smb_buf_length) + 4;
-
-       /* setting this here allows the code at the end of the function
-          to free the request buffer if there's an error */
-       resp_buf_type = CIFS_SMALL_BUFFER;
+       /* If true, rc here is expected and not an error */
+       if (sess_data->buf0_type != CIFS_NO_BUFFER &&
+           smb_buf->Status.CifsError ==
+                       cpu_to_le32(NT_STATUS_MORE_PROCESSING_REQUIRED))
+               rc = 0;
 
-       /* 2000 big enough to fit max user, domain, NOS name etc. */
-       str_area = kmalloc(2000, GFP_KERNEL);
-       if (str_area == NULL) {
-               rc = -ENOMEM;
-               goto ssetup_exit;
+       if (rc)
+               goto out;
+
+       cifs_dbg(FYI, "rawntlmssp session setup challenge phase\n");
+
+       if (smb_buf->WordCount != 4) {
+               rc = -EIO;
+               cifs_dbg(VFS, "bad word count %d\n", smb_buf->WordCount);
+               goto out;
        }
-       bcc_ptr = str_area;
 
-       iov[1].iov_base = NULL;
-       iov[1].iov_len = 0;
+       ses->Suid = smb_buf->Uid;   /* UID left in wire format (le) */
+       cifs_dbg(FYI, "UID = %llu\n", ses->Suid);
 
-       if (type == RawNTLMSSP) {
-               if ((pSMB->req.hdr.Flags2 & SMBFLG2_UNICODE) == 0) {
-                       cifs_dbg(VFS, "NTLMSSP requires Unicode support\n");
-                       rc = -ENOSYS;
-                       goto ssetup_exit;
-               }
+       bytes_remaining = get_bcc(smb_buf);
+       bcc_ptr = pByteArea(smb_buf);
 
-               cifs_dbg(FYI, "ntlmssp session setup phase %d\n", phase);
-               pSMB->req.hdr.Flags2 |= SMBFLG2_EXT_SEC;
-               capabilities |= CAP_EXTENDED_SECURITY;
-               pSMB->req.Capabilities |= cpu_to_le32(capabilities);
-               switch(phase) {
-               case NtLmNegotiate:
-                       build_ntlmssp_negotiate_blob(
-                               pSMB->req.SecurityBlob, ses);
-                       iov[1].iov_len = sizeof(NEGOTIATE_MESSAGE);
-                       iov[1].iov_base = pSMB->req.SecurityBlob;
-                       pSMB->req.SecurityBlobLength =
-                               cpu_to_le16(sizeof(NEGOTIATE_MESSAGE));
-                       break;
-               case NtLmAuthenticate:
-                       /*
-                        * 5 is an empirical value, large enough to hold
-                        * authenticate message plus max 10 of av paris,
-                        * domain, user, workstation names, flags, etc.
-                        */
-                       ntlmsspblob = kzalloc(
-                               5*sizeof(struct _AUTHENTICATE_MESSAGE),
-                               GFP_KERNEL);
-                       if (!ntlmsspblob) {
-                               rc = -ENOMEM;
-                               goto ssetup_exit;
-                       }
+       blob_len = le16_to_cpu(pSMB->resp.SecurityBlobLength);
+       if (blob_len > bytes_remaining) {
+               cifs_dbg(VFS, "bad security blob length %d\n",
+                               blob_len);
+               rc = -EINVAL;
+               goto out;
+       }
 
-                       rc = build_ntlmssp_auth_blob(ntlmsspblob,
-                                               &blob_len, ses, nls_cp);
-                       if (rc)
-                               goto ssetup_exit;
-                       iov[1].iov_len = blob_len;
-                       iov[1].iov_base = ntlmsspblob;
-                       pSMB->req.SecurityBlobLength = cpu_to_le16(blob_len);
-                       /*
-                        * Make sure that we tell the server that we are using
-                        * the uid that it just gave us back on the response
-                        * (challenge)
-                        */
-                       smb_buf->Uid = ses->Suid;
-                       break;
-               default:
-                       cifs_dbg(VFS, "invalid phase %d\n", phase);
-                       rc = -ENOSYS;
-                       goto ssetup_exit;
-               }
-               /* unicode strings must be word aligned */
-               if ((iov[0].iov_len + iov[1].iov_len) % 2) {
-                       *bcc_ptr = 0;
-                       bcc_ptr++;
-               }
-               unicode_oslm_strings(&bcc_ptr, nls_cp);
-       } else {
-               cifs_dbg(VFS, "secType %d not supported!\n", type);
-               rc = -ENOSYS;
-               goto ssetup_exit;
+       rc = decode_ntlmssp_challenge(bcc_ptr, blob_len, ses);
+out:
+       sess_free_buffer(sess_data);
+
+       if (!rc) {
+               sess_data->func = sess_auth_rawntlmssp_authenticate;
+               return;
        }
 
-       iov[2].iov_base = str_area;
-       iov[2].iov_len = (long) bcc_ptr - (long) str_area;
+       /* Else error. Cleanup */
+       kfree(ses->auth_key.response);
+       ses->auth_key.response = NULL;
+       kfree(ses->ntlmssp);
+       ses->ntlmssp = NULL;
 
-       count = iov[1].iov_len + iov[2].iov_len;
-       smb_buf->smb_buf_length =
-               cpu_to_be32(be32_to_cpu(smb_buf->smb_buf_length) + count);
+       sess_data->func = NULL;
+       sess_data->result = rc;
+}
 
-       put_bcc(count, smb_buf);
+static void
+sess_auth_rawntlmssp_authenticate(struct sess_data *sess_data)
+{
+       int rc;
+       struct smb_hdr *smb_buf;
+       SESSION_SETUP_ANDX *pSMB;
+       struct cifs_ses *ses = sess_data->ses;
+       __u16 bytes_remaining;
+       char *bcc_ptr;
+       char *ntlmsspblob = NULL;
+       u16 blob_len;
 
-       rc = SendReceive2(xid, ses, iov, 3 /* num_iovecs */, &resp_buf_type,
-                         CIFS_LOG_ERROR);
-       /* SMB request buf freed in SendReceive2 */
+       cifs_dbg(FYI, "rawntlmssp session setup authenticate phase\n");
 
-       pSMB = (SESSION_SETUP_ANDX *)iov[0].iov_base;
-       smb_buf = (struct smb_hdr *)iov[0].iov_base;
+       /* wct = 12 */
+       rc = sess_alloc_buffer(sess_data, 12);
+       if (rc)
+               goto out;
 
-       if ((type == RawNTLMSSP) && (resp_buf_type != CIFS_NO_BUFFER) &&
-           (smb_buf->Status.CifsError ==
-                       cpu_to_le32(NT_STATUS_MORE_PROCESSING_REQUIRED))) {
-               if (phase != NtLmNegotiate) {
-                       cifs_dbg(VFS, "Unexpected more processing error\n");
-                       goto ssetup_exit;
-               }
-               /* NTLMSSP Negotiate sent now processing challenge (response) */
-               phase = NtLmChallenge; /* process ntlmssp challenge */
-               rc = 0; /* MORE_PROC rc is not an error here, but expected */
+       /* Build security blob before we assemble the request */
+       pSMB = (SESSION_SETUP_ANDX *)sess_data->iov[0].iov_base;
+       smb_buf = (struct smb_hdr *)pSMB;
+       /*
+        * 5 is an empirical value, large enough to hold
+        * authenticate message plus max 10 of av paris,
+        * domain, user, workstation names, flags, etc.
+        */
+       ntlmsspblob = kzalloc(5*sizeof(struct _AUTHENTICATE_MESSAGE),
+                               GFP_KERNEL);
+       if (!ntlmsspblob) {
+               rc = -ENOMEM;
+               goto out;
        }
+
+       rc = build_ntlmssp_auth_blob(ntlmsspblob,
+                                       &blob_len, ses, sess_data->nls_cp);
+       if (rc)
+               goto out_free_ntlmsspblob;
+       sess_data->iov[1].iov_len = blob_len;
+       sess_data->iov[1].iov_base = ntlmsspblob;
+       pSMB->req.SecurityBlobLength = cpu_to_le16(blob_len);
+       /*
+        * Make sure that we tell the server that we are using
+        * the uid that it just gave us back on the response
+        * (challenge)
+        */
+       smb_buf->Uid = ses->Suid;
+
+       rc = _sess_auth_rawntlmssp_assemble_req(sess_data);
        if (rc)
-               goto ssetup_exit;
+               goto out_free_ntlmsspblob;
 
+       rc = sess_sendreceive(sess_data);
+       if (rc)
+               goto out_free_ntlmsspblob;
+
+       pSMB = (SESSION_SETUP_ANDX *)sess_data->iov[0].iov_base;
+       smb_buf = (struct smb_hdr *)sess_data->iov[0].iov_base;
        if (smb_buf->WordCount != 4) {
                rc = -EIO;
                cifs_dbg(VFS, "bad word count %d\n", smb_buf->WordCount);
-               goto ssetup_exit;
+               goto out_free_ntlmsspblob;
        }
-       action = le16_to_cpu(pSMB->resp.Action);
-       if (action & GUEST_LOGIN)
+
+       if (le16_to_cpu(pSMB->resp.Action) & GUEST_LOGIN)
                cifs_dbg(FYI, "Guest login\n"); /* BB mark SesInfo struct? */
-       ses->Suid = smb_buf->Uid;   /* UID left in wire format (le) */
-       cifs_dbg(FYI, "UID = %llu\n", ses->Suid);
-       /* response can have either 3 or 4 word count - Samba sends 3 */
-       /* and lanman response is 3 */
+
        bytes_remaining = get_bcc(smb_buf);
        bcc_ptr = pByteArea(smb_buf);
-
        blob_len = le16_to_cpu(pSMB->resp.SecurityBlobLength);
        if (blob_len > bytes_remaining) {
                cifs_dbg(VFS, "bad security blob length %d\n",
-                        blob_len);
+                               blob_len);
                rc = -EINVAL;
-               goto ssetup_exit;
-       }
-       if (phase == NtLmChallenge) {
-               rc = decode_ntlmssp_challenge(bcc_ptr, blob_len, ses);
-               if (rc)
-                       goto ssetup_exit;
+               goto out_free_ntlmsspblob;
        }
        bcc_ptr += blob_len;
        bytes_remaining -= blob_len;
 
+
        /* BB check if Unicode and decode strings */
        if (bytes_remaining == 0) {
                /* no string area to decode, do nothing */
@@ -1357,61 +1341,113 @@ ssetup_ntlmssp_authenticate:
                        ++bcc_ptr;
                        --bytes_remaining;
                }
-               decode_unicode_ssetup(&bcc_ptr, bytes_remaining, ses, nls_cp);
+               decode_unicode_ssetup(&bcc_ptr, bytes_remaining, ses,
+                                     sess_data->nls_cp);
        } else {
-               decode_ascii_ssetup(&bcc_ptr, bytes_remaining, ses, nls_cp);
+               decode_ascii_ssetup(&bcc_ptr, bytes_remaining, ses,
+                                   sess_data->nls_cp);
        }
 
-ssetup_exit:
-       kfree(str_area);
+out_free_ntlmsspblob:
        kfree(ntlmsspblob);
-       ntlmsspblob = NULL;
-       if (resp_buf_type == CIFS_SMALL_BUFFER) {
-               cifs_dbg(FYI, "ssetup freeing small buf %p\n", iov[0].iov_base);
-               cifs_small_buf_release(iov[0].iov_base);
-       } else if (resp_buf_type == CIFS_LARGE_BUFFER)
-               cifs_buf_release(iov[0].iov_base);
-
-       /* if ntlmssp, and negotiate succeeded, proceed to authenticate phase */
-       if ((phase == NtLmChallenge) && (rc == 0))
-               goto ssetup_ntlmssp_authenticate;
-
-       if (!rc) {
-               mutex_lock(&ses->server->srv_mutex);
-               if (!ses->server->session_estab) {
-                       if (ses->server->sign) {
-                               ses->server->session_key.response =
-                                       kmemdup(ses->auth_key.response,
-                                       ses->auth_key.len, GFP_KERNEL);
-                               if (!ses->server->session_key.response) {
-                                       rc = -ENOMEM;
-                                       mutex_unlock(&ses->server->srv_mutex);
-                                       goto keycp_exit;
-                               }
-                               ses->server->session_key.len =
-                                                       ses->auth_key.len;
-                       }
-                       ses->server->sequence_number = 0x2;
-                       ses->server->session_estab = true;
-               }
-               mutex_unlock(&ses->server->srv_mutex);
+out:
+       sess_free_buffer(sess_data);
 
-               cifs_dbg(FYI, "CIFS session established successfully\n");
-               spin_lock(&GlobalMid_Lock);
-               ses->status = CifsGood;
-               ses->need_reconnect = false;
-               spin_unlock(&GlobalMid_Lock);
-       }
+        if (!rc)
+               rc = sess_establish_session(sess_data);
 
-keycp_exit:
+       /* Cleanup */
        kfree(ses->auth_key.response);
        ses->auth_key.response = NULL;
        kfree(ses->ntlmssp);
+       ses->ntlmssp = NULL;
 
-       return rc;
+       sess_data->func = NULL;
+       sess_data->result = rc;
+}
 
-out:
+int select_sec(struct cifs_ses *ses, struct sess_data *sess_data)
+{
+       int type;
+
+       type = select_sectype(ses->server, ses->sectype);
+       cifs_dbg(FYI, "sess setup type %d\n", type);
+       if (type == Unspecified) {
+               cifs_dbg(VFS,
+                       "Unable to select appropriate authentication method!");
+               return -EINVAL;
+       }
+
+       switch (type) {
+       case LANMAN:
+               /* LANMAN and plaintext are less secure and off by default.
+                * So we make this explicitly be turned on in kconfig (in the
+                * build) and turned on at runtime (changed from the default)
+                * in proc/fs/cifs or via mount parm.  Unfortunately this is
+                * needed for old Win (e.g. Win95), some obscure NAS and OS/2 */
+#ifdef CONFIG_CIFS_WEAK_PW_HASH
+               sess_data->func = sess_auth_lanman;
+               break;
+#else
+               return -EOPNOTSUPP;
+#endif
+       case NTLM:
+               sess_data->func = sess_auth_ntlm;
+               break;
+       case NTLMv2:
+               sess_data->func = sess_auth_ntlmv2;
+               break;
+       case Kerberos:
+#ifdef CONFIG_CIFS_UPCALL
+               sess_data->func = sess_auth_kerberos;
+               break;
+#else
+               cifs_dbg(VFS, "Kerberos negotiated but upcall support disabled!\n");
+               return -ENOSYS;
+               break;
+#endif /* CONFIG_CIFS_UPCALL */
+       case RawNTLMSSP:
+               sess_data->func = sess_auth_rawntlmssp_negotiate;
+               break;
+       default:
+               cifs_dbg(VFS, "secType %d not supported!\n", type);
+               return -ENOSYS;
+       }
+
+       return 0;
+}
+
+int CIFS_SessSetup(const unsigned int xid, struct cifs_ses *ses,
+                   const struct nls_table *nls_cp)
+{
+       int rc = 0;
+       struct sess_data *sess_data;
+
+       if (ses == NULL) {
+               WARN(1, "%s: ses == NULL!", __func__);
+               return -EINVAL;
+       }
+
+       sess_data = kzalloc(sizeof(struct sess_data), GFP_KERNEL);
+       if (!sess_data)
+               return -ENOMEM;
+
+       rc = select_sec(ses, sess_data);
+       if (rc)
+               goto out;
+
+       sess_data->xid = xid;
+       sess_data->ses = ses;
+       sess_data->buf0_type = CIFS_NO_BUFFER;
+       sess_data->nls_cp = (struct nls_table *) nls_cp;
+
+       while (sess_data->func)
+               sess_data->func(sess_data);
+
+       /* Store result before we free sess_data */
        rc = sess_data->result;
+
+out:
        kfree(sess_data);
        return rc;
 }