cifs: Split lanman auth from CIFS_SessSetup()
authorSachin Prabhu <sprabhu@redhat.com>
Mon, 16 Jun 2014 14:35:25 +0000 (15:35 +0100)
committerSteve French <smfrench@gmail.com>
Fri, 1 Aug 2014 04:11:15 +0000 (23:11 -0500)
In preparation for splitting CIFS_SessSetup() into smaller more
manageable chunks, we first add helper functions.

We then proceed to split out lanman auth out of CIFS_SessSetup()

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 e87387dbf39fa1d24b19f245e86da4c907b0fdaf..36d0b908f8b44d1f9281033eeeb14146a4541ed8 100644 (file)
@@ -520,6 +520,240 @@ select_sectype(struct TCP_Server_Info *server, enum securityEnum requested)
        }
 }
 
+struct sess_data {
+       unsigned int xid;
+       struct cifs_ses *ses;
+       struct nls_table *nls_cp;
+       void (*func)(struct sess_data *);
+       int result;
+
+       /* 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
+        */
+       int buf0_type;
+       struct kvec iov[3];
+};
+
+static int
+sess_alloc_buffer(struct sess_data *sess_data, int wct)
+{
+       int rc;
+       struct cifs_ses *ses = sess_data->ses;
+       struct smb_hdr *smb_buf;
+
+       rc = small_smb_init_no_tc(SMB_COM_SESSION_SETUP_ANDX, wct, ses,
+                                 (void **)&smb_buf);
+
+       if (rc)
+               return rc;
+
+       sess_data->iov[0].iov_base = (char *)smb_buf;
+       sess_data->iov[0].iov_len = be32_to_cpu(smb_buf->smb_buf_length) + 4;
+       /*
+        * This variable will be used to clear the buffer
+        * allocated above in case of any error in the calling function.
+        */
+       sess_data->buf0_type = CIFS_SMALL_BUFFER;
+
+       /* 2000 big enough to fit max user, domain, NOS name etc. */
+       sess_data->iov[2].iov_base = kmalloc(2000, GFP_KERNEL);
+       if (!sess_data->iov[2].iov_base) {
+               rc = -ENOMEM;
+               goto out_free_smb_buf;
+       }
+
+       return 0;
+
+out_free_smb_buf:
+       kfree(smb_buf);
+       sess_data->iov[0].iov_base = NULL;
+       sess_data->iov[0].iov_len = 0;
+       sess_data->buf0_type = CIFS_NO_BUFFER;
+       return rc;
+}
+
+static void
+sess_free_buffer(struct sess_data *sess_data)
+{
+
+       free_rsp_buf(sess_data->buf0_type, sess_data->iov[0].iov_base);
+       sess_data->buf0_type = CIFS_NO_BUFFER;
+       kfree(sess_data->iov[2].iov_base);
+}
+
+static int
+sess_establish_session(struct sess_data *sess_data)
+{
+       struct cifs_ses *ses = sess_data->ses;
+
+       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) {
+                               mutex_unlock(&ses->server->srv_mutex);
+                               return -ENOMEM;
+                       }
+                       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);
+
+       cifs_dbg(FYI, "CIFS session established successfully\n");
+       spin_lock(&GlobalMid_Lock);
+       ses->status = CifsGood;
+       ses->need_reconnect = false;
+       spin_unlock(&GlobalMid_Lock);
+
+       return 0;
+}
+
+static int
+sess_sendreceive(struct sess_data *sess_data)
+{
+       int rc;
+       struct smb_hdr *smb_buf = (struct smb_hdr *) sess_data->iov[0].iov_base;
+       __u16 count;
+
+       count = sess_data->iov[1].iov_len + sess_data->iov[2].iov_len;
+       smb_buf->smb_buf_length =
+               cpu_to_be32(be32_to_cpu(smb_buf->smb_buf_length) + count);
+       put_bcc(count, smb_buf);
+
+       rc = SendReceive2(sess_data->xid, sess_data->ses,
+                         sess_data->iov, 3 /* num_iovecs */,
+                         &sess_data->buf0_type,
+                         CIFS_LOG_ERROR);
+
+       return rc;
+}
+
+/*
+ * 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
+static void
+sess_auth_lanman(struct sess_data *sess_data)
+{
+       int rc = 0;
+       struct smb_hdr *smb_buf;
+       SESSION_SETUP_ANDX *pSMB;
+       char *bcc_ptr;
+       struct cifs_ses *ses = sess_data->ses;
+       char lnm_session_key[CIFS_AUTH_RESP_SIZE];
+       __u32 capabilities;
+       __u16 bytes_remaining;
+
+       /* lanman 2 style sessionsetup */
+       /* wct = 10 */
+       rc = sess_alloc_buffer(sess_data, 10);
+       if (rc)
+               goto out;
+
+       pSMB = (SESSION_SETUP_ANDX *)sess_data->iov[0].iov_base;
+       bcc_ptr = sess_data->iov[2].iov_base;
+       capabilities = cifs_ssetup_hdr(ses, pSMB);
+
+       pSMB->req.hdr.Flags2 &= ~SMBFLG2_UNICODE;
+
+       /* no capabilities flags in old lanman negotiation */
+       pSMB->old_req.PasswordLength = cpu_to_le16(CIFS_AUTH_RESP_SIZE);
+
+       /* Calculate hash with password and copy into bcc_ptr.
+        * Encryption Key (stored as in cryptkey) gets used if the
+        * security mode bit in Negottiate Protocol response states
+        * to use challenge/response method (i.e. Password bit is 1).
+        */
+       rc = calc_lanman_hash(ses->password, ses->server->cryptkey,
+                             ses->server->sec_mode & SECMODE_PW_ENCRYPT ?
+                             true : false, lnm_session_key);
+
+       memcpy(bcc_ptr, (char *)lnm_session_key, CIFS_AUTH_RESP_SIZE);
+       bcc_ptr += CIFS_AUTH_RESP_SIZE;
+
+       /*
+        * can not sign if LANMAN negotiated so no need
+        * to calculate signing key? but what if server
+        * changed to do higher than lanman dialect and
+        * we reconnected would we ever calc signing_key?
+        */
+
+       cifs_dbg(FYI, "Negotiating LANMAN setting up strings\n");
+       /* Unicode not allowed for LANMAN dialects */
+       ascii_ssetup_strings(&bcc_ptr, ses, sess_data->nls_cp);
+
+       sess_data->iov[2].iov_len = (long) bcc_ptr -
+                       (long) sess_data->iov[2].iov_base;
+
+       rc = sess_sendreceive(sess_data);
+       if (rc)
+               goto out;
+
+       pSMB = (SESSION_SETUP_ANDX *)sess_data->iov[0].iov_base;
+       smb_buf = (struct smb_hdr *)sess_data->iov[0].iov_base;
+
+       /* lanman response has a word count of 3 */
+       if (smb_buf->WordCount != 3) {
+               rc = -EIO;
+               cifs_dbg(VFS, "bad word count %d\n", smb_buf->WordCount);
+               goto out;
+       }
+
+       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);
+
+       bytes_remaining = get_bcc(smb_buf);
+       bcc_ptr = pByteArea(smb_buf);
+
+       /* BB check if Unicode and decode strings */
+       if (bytes_remaining == 0) {
+               /* no string area to decode, do nothing */
+       } else if (smb_buf->Flags2 & SMBFLG2_UNICODE) {
+               /* unicode string area must be word-aligned */
+               if (((unsigned long) bcc_ptr - (unsigned long) smb_buf) % 2) {
+                       ++bcc_ptr;
+                       --bytes_remaining;
+               }
+               decode_unicode_ssetup(&bcc_ptr, bytes_remaining, ses,
+                                     sess_data->nls_cp);
+       } else {
+               decode_ascii_ssetup(&bcc_ptr, bytes_remaining, ses,
+                                   sess_data->nls_cp);
+       }
+
+       rc = sess_establish_session(sess_data);
+out:
+       sess_data->result = rc;
+       sess_data->func = NULL;
+       sess_free_buffer(sess_data);
+}
+
+#else
+
+static void
+sess_auth_lanman(struct sess_data *sess_data)
+{
+       sess_data->result = -EOPNOTSUPP;
+       sess_data->func = NULL;
+}
+#endif
+
 int
 CIFS_SessSetup(const unsigned int xid, struct cifs_ses *ses,
               const struct nls_table *nls_cp)
@@ -540,12 +774,21 @@ CIFS_SessSetup(const unsigned int xid, struct cifs_ses *ses,
        __le32 phase = NtLmNegotiate; /* NTLMSSP, if needed, is multistage */
        u16 blob_len;
        char *ntlmsspblob = NULL;
+       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;
+       sess_data->xid = xid;
+       sess_data->ses = ses;
+       sess_data->buf0_type = CIFS_NO_BUFFER;
+       sess_data->nls_cp = (struct nls_table *) nls_cp;
+
        type = select_sectype(ses->server, ses->sectype);
        cifs_dbg(FYI, "sess setup type %d\n", type);
        if (type == Unspecified) {
@@ -554,6 +797,14 @@ CIFS_SessSetup(const unsigned int xid, struct cifs_ses *ses,
                return -EINVAL;
        }
 
+       switch (type) {
+       case LANMAN:
+               sess_auth_lanman(sess_data);
+               goto out;
+       default:
+               cifs_dbg(FYI, "Continuing with CIFS_SessSetup\n");
+       }
+
        if (type == RawNTLMSSP) {
                /* if memory allocation is successful, caller of this function
                 * frees it.
@@ -569,17 +820,7 @@ ssetup_ntlmssp_authenticate:
        if (phase == NtLmChallenge)
                phase = NtLmAuthenticate; /* if ntlmssp, now final phase */
 
-       if (type == LANMAN) {
-#ifndef CONFIG_CIFS_WEAK_PW_HASH
-               /* 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 */
-               return -EOPNOTSUPP;
-#endif
-               wct = 10; /* lanman 2 style sessionsetup */
-       } else if ((type == NTLM) || (type == NTLMv2)) {
+       if ((type == NTLM) || (type == NTLMv2)) {
                /* For NTLMv2 failures eventually may need to retry NTLM */
                wct = 13; /* old style NTLM sessionsetup */
        } else /* same size: negotiate or auth, NTLMSSP or extended security */
@@ -618,39 +859,7 @@ ssetup_ntlmssp_authenticate:
        iov[1].iov_base = NULL;
        iov[1].iov_len = 0;
 
-       if (type == LANMAN) {
-#ifdef CONFIG_CIFS_WEAK_PW_HASH
-               char lnm_session_key[CIFS_AUTH_RESP_SIZE];
-
-               pSMB->req.hdr.Flags2 &= ~SMBFLG2_UNICODE;
-
-               /* no capabilities flags in old lanman negotiation */
-
-               pSMB->old_req.PasswordLength = cpu_to_le16(CIFS_AUTH_RESP_SIZE);
-
-               /* Calculate hash with password and copy into bcc_ptr.
-                * Encryption Key (stored as in cryptkey) gets used if the
-                * security mode bit in Negottiate Protocol response states
-                * to use challenge/response method (i.e. Password bit is 1).
-                */
-
-               rc = calc_lanman_hash(ses->password, ses->server->cryptkey,
-                                ses->server->sec_mode & SECMODE_PW_ENCRYPT ?
-                                       true : false, lnm_session_key);
-
-               memcpy(bcc_ptr, (char *)lnm_session_key, CIFS_AUTH_RESP_SIZE);
-               bcc_ptr += CIFS_AUTH_RESP_SIZE;
-
-               /* can not sign if LANMAN negotiated so no need
-               to calculate signing key? but what if server
-               changed to do higher than lanman dialect and
-               we reconnected would we ever calc signing_key? */
-
-               cifs_dbg(FYI, "Negotiating LANMAN setting up strings\n");
-               /* Unicode not allowed for LANMAN dialects */
-               ascii_ssetup_strings(&bcc_ptr, ses, nls_cp);
-#endif
-       } else if (type == NTLM) {
+       if (type == NTLM) {
                pSMB->req_no_secext.Capabilities = cpu_to_le32(capabilities);
                pSMB->req_no_secext.CaseInsensitivePasswordLength =
                        cpu_to_le16(CIFS_AUTH_RESP_SIZE);
@@ -889,7 +1098,6 @@ ssetup_ntlmssp_authenticate:
                }
                if (phase == NtLmChallenge) {
                        rc = decode_ntlmssp_challenge(bcc_ptr, blob_len, ses);
-                       /* now goto beginning for ntlmssp authenticate phase */
                        if (rc)
                                goto ssetup_exit;
                }
@@ -962,4 +1170,9 @@ keycp_exit:
        kfree(ses->ntlmssp);
 
        return rc;
+
+out:
+       rc = sess_data->result;
+       kfree(sess_data);
+       return rc;
 }