summaryrefslogtreecommitdiffstats
path: root/fs/cifs/sess.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/cifs/sess.c')
-rw-r--r--fs/cifs/sess.c373
1 files changed, 297 insertions, 76 deletions
diff --git a/fs/cifs/sess.c b/fs/cifs/sess.c
index 5c68b4282be9..897a052270f9 100644
--- a/fs/cifs/sess.c
+++ b/fs/cifs/sess.c
@@ -3,7 +3,7 @@
*
* SMB/CIFS session setup handling routines
*
- * Copyright (c) International Business Machines Corp., 2006, 2007
+ * Copyright (c) International Business Machines Corp., 2006, 2009
* Author(s): Steve French (sfrench@us.ibm.com)
*
* This library is free software; you can redistribute it and/or modify
@@ -111,7 +111,7 @@ static __le16 get_next_vcnum(struct cifsSesInfo *ses)
get_vc_num_exit:
write_unlock(&cifs_tcp_ses_lock);
- return le16_to_cpu(vcnum);
+ return cpu_to_le16(vcnum);
}
static __u32 cifs_ssetup_hdr(struct cifsSesInfo *ses, SESSION_SETUP_ANDX *pSMB)
@@ -277,85 +277,51 @@ static void ascii_ssetup_strings(char **pbcc_area, struct cifsSesInfo *ses,
*pbcc_area = bcc_ptr;
}
-static int decode_unicode_ssetup(char **pbcc_area, int bleft,
- struct cifsSesInfo *ses,
- const struct nls_table *nls_cp)
+static void
+decode_unicode_ssetup(char **pbcc_area, int bleft, struct cifsSesInfo *ses,
+ const struct nls_table *nls_cp)
{
- int rc = 0;
- int words_left, len;
+ int len;
char *data = *pbcc_area;
-
-
cFYI(1, ("bleft %d", bleft));
-
- /* SMB header is unaligned, so cifs servers word align start of
- Unicode strings */
- data++;
- bleft--; /* Windows servers do not always double null terminate
- their final Unicode string - in which case we
- now will not attempt to decode the byte of junk
- which follows it */
-
- words_left = bleft / 2;
-
- /* save off server operating system */
- len = UniStrnlen((wchar_t *) data, words_left);
-
-/* We look for obvious messed up bcc or strings in response so we do not go off
- the end since (at least) WIN2K and Windows XP have a major bug in not null
- terminating last Unicode string in response */
- if (len >= words_left)
- return rc;
+ /*
+ * Windows servers do not always double null terminate their final
+ * Unicode string. Check to see if there are an uneven number of bytes
+ * left. If so, then add an extra NULL pad byte to the end of the
+ * response.
+ *
+ * See section 2.7.2 in "Implementing CIFS" for details
+ */
+ if (bleft % 2) {
+ data[bleft] = 0;
+ ++bleft;
+ }
kfree(ses->serverOS);
- /* UTF-8 string will not grow more than four times as big as UCS-16 */
- ses->serverOS = kzalloc((4 * len) + 2 /* trailing null */, GFP_KERNEL);
- if (ses->serverOS != NULL)
- cifs_strfromUCS_le(ses->serverOS, (__le16 *)data, len, nls_cp);
- data += 2 * (len + 1);
- words_left -= len + 1;
-
- /* save off server network operating system */
- len = UniStrnlen((wchar_t *) data, words_left);
-
- if (len >= words_left)
- return rc;
+ ses->serverOS = cifs_strndup_from_ucs(data, bleft, true, nls_cp);
+ cFYI(1, ("serverOS=%s", ses->serverOS));
+ len = (UniStrnlen((wchar_t *) data, bleft / 2) * 2) + 2;
+ data += len;
+ bleft -= len;
+ if (bleft <= 0)
+ return;
kfree(ses->serverNOS);
- ses->serverNOS = kzalloc((4 * len) + 2 /* trailing null */, GFP_KERNEL);
- if (ses->serverNOS != NULL) {
- cifs_strfromUCS_le(ses->serverNOS, (__le16 *)data, len,
- nls_cp);
- if (strncmp(ses->serverNOS, "NT LAN Manager 4", 16) == 0) {
- cFYI(1, ("NT4 server"));
- ses->flags |= CIFS_SES_NT4;
- }
- }
- data += 2 * (len + 1);
- words_left -= len + 1;
-
- /* save off server domain */
- len = UniStrnlen((wchar_t *) data, words_left);
-
- if (len > words_left)
- return rc;
+ ses->serverNOS = cifs_strndup_from_ucs(data, bleft, true, nls_cp);
+ cFYI(1, ("serverNOS=%s", ses->serverNOS));
+ len = (UniStrnlen((wchar_t *) data, bleft / 2) * 2) + 2;
+ data += len;
+ bleft -= len;
+ if (bleft <= 0)
+ return;
kfree(ses->serverDomain);
- ses->serverDomain = kzalloc(2 * (len + 1), GFP_KERNEL); /* BB FIXME wrong length */
- if (ses->serverDomain != NULL) {
- cifs_strfromUCS_le(ses->serverDomain, (__le16 *)data, len,
- nls_cp);
- ses->serverDomain[2*len] = 0;
- ses->serverDomain[(2*len) + 1] = 0;
- }
- data += 2 * (len + 1);
- words_left -= len + 1;
+ ses->serverDomain = cifs_strndup_from_ucs(data, bleft, true, nls_cp);
+ cFYI(1, ("serverDomain=%s", ses->serverDomain));
- cFYI(1, ("words left: %d", words_left));
-
- return rc;
+ return;
}
static int decode_ascii_ssetup(char **pbcc_area, int bleft,
@@ -412,6 +378,186 @@ static int decode_ascii_ssetup(char **pbcc_area, int bleft,
return rc;
}
+static int decode_ntlmssp_challenge(char *bcc_ptr, int blob_len,
+ struct cifsSesInfo *ses)
+{
+ CHALLENGE_MESSAGE *pblob = (CHALLENGE_MESSAGE *)bcc_ptr;
+
+ if (blob_len < sizeof(CHALLENGE_MESSAGE)) {
+ cERROR(1, ("challenge blob len %d too small", blob_len));
+ return -EINVAL;
+ }
+
+ if (memcmp(pblob->Signature, "NTLMSSP", 8)) {
+ cERROR(1, ("blob signature incorrect %s", pblob->Signature));
+ return -EINVAL;
+ }
+ if (pblob->MessageType != NtLmChallenge) {
+ cERROR(1, ("Incorrect message type %d", pblob->MessageType));
+ return -EINVAL;
+ }
+
+ memcpy(ses->server->cryptKey, pblob->Challenge, CIFS_CRYPTO_KEY_SIZE);
+ /* BB we could decode pblob->NegotiateFlags; some may be useful */
+ /* In particular we can examine sign flags */
+ /* BB spec says that if AvId field of MsvAvTimestamp is populated then
+ we must set the MIC field of the AUTHENTICATE_MESSAGE */
+
+ return 0;
+}
+
+#ifdef CONFIG_CIFS_EXPERIMENTAL
+/* BB Move to ntlmssp.c eventually */
+
+/* We do not malloc the blob, it is passed in pbuffer, because
+ it is fixed size, and small, making this approach cleaner */
+static void build_ntlmssp_negotiate_blob(unsigned char *pbuffer,
+ struct cifsSesInfo *ses)
+{
+ NEGOTIATE_MESSAGE *sec_blob = (NEGOTIATE_MESSAGE *)pbuffer;
+ __u32 flags;
+
+ memcpy(sec_blob->Signature, NTLMSSP_SIGNATURE, 8);
+ sec_blob->MessageType = NtLmNegotiate;
+
+ /* BB is NTLMV2 session security format easier to use here? */
+ flags = NTLMSSP_NEGOTIATE_56 | NTLMSSP_REQUEST_TARGET |
+ NTLMSSP_NEGOTIATE_128 | NTLMSSP_NEGOTIATE_UNICODE |
+ NTLMSSP_NEGOTIATE_NT_ONLY | NTLMSSP_NEGOTIATE_NTLM;
+ if (ses->server->secMode &
+ (SECMODE_SIGN_REQUIRED | SECMODE_SIGN_ENABLED))
+ flags |= NTLMSSP_NEGOTIATE_SIGN;
+ if (ses->server->secMode & SECMODE_SIGN_REQUIRED)
+ flags |= NTLMSSP_NEGOTIATE_ALWAYS_SIGN;
+
+ sec_blob->NegotiateFlags |= cpu_to_le32(flags);
+
+ sec_blob->WorkstationName.BufferOffset = 0;
+ sec_blob->WorkstationName.Length = 0;
+ sec_blob->WorkstationName.MaximumLength = 0;
+
+ /* Domain name is sent on the Challenge not Negotiate NTLMSSP request */
+ sec_blob->DomainName.BufferOffset = 0;
+ sec_blob->DomainName.Length = 0;
+ sec_blob->DomainName.MaximumLength = 0;
+}
+
+/* We do not malloc the blob, it is passed in pbuffer, because its
+ maximum possible size is fixed and small, making this approach cleaner.
+ This function returns the length of the data in the blob */
+static int build_ntlmssp_auth_blob(unsigned char *pbuffer,
+ struct cifsSesInfo *ses,
+ const struct nls_table *nls_cp, int first)
+{
+ AUTHENTICATE_MESSAGE *sec_blob = (AUTHENTICATE_MESSAGE *)pbuffer;
+ __u32 flags;
+ unsigned char *tmp;
+ char ntlm_session_key[CIFS_SESS_KEY_SIZE];
+
+ memcpy(sec_blob->Signature, NTLMSSP_SIGNATURE, 8);
+ sec_blob->MessageType = NtLmAuthenticate;
+
+ flags = NTLMSSP_NEGOTIATE_56 |
+ NTLMSSP_REQUEST_TARGET | NTLMSSP_NEGOTIATE_TARGET_INFO |
+ NTLMSSP_NEGOTIATE_128 | NTLMSSP_NEGOTIATE_UNICODE |
+ NTLMSSP_NEGOTIATE_NT_ONLY | NTLMSSP_NEGOTIATE_NTLM;
+ if (ses->server->secMode &
+ (SECMODE_SIGN_REQUIRED | SECMODE_SIGN_ENABLED))
+ flags |= NTLMSSP_NEGOTIATE_SIGN;
+ if (ses->server->secMode & SECMODE_SIGN_REQUIRED)
+ flags |= NTLMSSP_NEGOTIATE_ALWAYS_SIGN;
+
+ tmp = pbuffer + sizeof(AUTHENTICATE_MESSAGE);
+ sec_blob->NegotiateFlags |= cpu_to_le32(flags);
+
+ sec_blob->LmChallengeResponse.BufferOffset =
+ cpu_to_le32(sizeof(AUTHENTICATE_MESSAGE));
+ sec_blob->LmChallengeResponse.Length = 0;
+ sec_blob->LmChallengeResponse.MaximumLength = 0;
+
+ /* calculate session key, BB what about adding similar ntlmv2 path? */
+ SMBNTencrypt(ses->password, ses->server->cryptKey, ntlm_session_key);
+ if (first)
+ cifs_calculate_mac_key(&ses->server->mac_signing_key,
+ ntlm_session_key, ses->password);
+
+ memcpy(tmp, ntlm_session_key, CIFS_SESS_KEY_SIZE);
+ sec_blob->NtChallengeResponse.BufferOffset = cpu_to_le32(tmp - pbuffer);
+ sec_blob->NtChallengeResponse.Length = cpu_to_le16(CIFS_SESS_KEY_SIZE);
+ sec_blob->NtChallengeResponse.MaximumLength =
+ cpu_to_le16(CIFS_SESS_KEY_SIZE);
+
+ tmp += CIFS_SESS_KEY_SIZE;
+
+ if (ses->domainName == NULL) {
+ sec_blob->DomainName.BufferOffset = cpu_to_le32(tmp - pbuffer);
+ sec_blob->DomainName.Length = 0;
+ sec_blob->DomainName.MaximumLength = 0;
+ tmp += 2;
+ } else {
+ int len;
+ len = cifs_strtoUCS((__le16 *)tmp, ses->domainName,
+ MAX_USERNAME_SIZE, nls_cp);
+ len *= 2; /* unicode is 2 bytes each */
+ len += 2; /* trailing null */
+ sec_blob->DomainName.BufferOffset = cpu_to_le32(tmp - pbuffer);
+ sec_blob->DomainName.Length = cpu_to_le16(len);
+ sec_blob->DomainName.MaximumLength = cpu_to_le16(len);
+ tmp += len;
+ }
+
+ if (ses->userName == NULL) {
+ sec_blob->UserName.BufferOffset = cpu_to_le32(tmp - pbuffer);
+ sec_blob->UserName.Length = 0;
+ sec_blob->UserName.MaximumLength = 0;
+ tmp += 2;
+ } else {
+ int len;
+ len = cifs_strtoUCS((__le16 *)tmp, ses->userName,
+ MAX_USERNAME_SIZE, nls_cp);
+ len *= 2; /* unicode is 2 bytes each */
+ len += 2; /* trailing null */
+ sec_blob->UserName.BufferOffset = cpu_to_le32(tmp - pbuffer);
+ sec_blob->UserName.Length = cpu_to_le16(len);
+ sec_blob->UserName.MaximumLength = cpu_to_le16(len);
+ tmp += len;
+ }
+
+ sec_blob->WorkstationName.BufferOffset = cpu_to_le32(tmp - pbuffer);
+ sec_blob->WorkstationName.Length = 0;
+ sec_blob->WorkstationName.MaximumLength = 0;
+ tmp += 2;
+
+ sec_blob->SessionKey.BufferOffset = cpu_to_le32(tmp - pbuffer);
+ sec_blob->SessionKey.Length = 0;
+ sec_blob->SessionKey.MaximumLength = 0;
+ return tmp - pbuffer;
+}
+
+
+static void setup_ntlmssp_neg_req(SESSION_SETUP_ANDX *pSMB,
+ struct cifsSesInfo *ses)
+{
+ build_ntlmssp_negotiate_blob(&pSMB->req.SecurityBlob[0], ses);
+ pSMB->req.SecurityBlobLength = cpu_to_le16(sizeof(NEGOTIATE_MESSAGE));
+
+ return;
+}
+
+static int setup_ntlmssp_auth_req(SESSION_SETUP_ANDX *pSMB,
+ struct cifsSesInfo *ses,
+ const struct nls_table *nls, int first_time)
+{
+ int bloblen;
+
+ bloblen = build_ntlmssp_auth_blob(&pSMB->req.SecurityBlob[0], ses, nls,
+ first_time);
+ pSMB->req.SecurityBlobLength = cpu_to_le16(bloblen);
+
+ return bloblen;
+}
+#endif
+
int
CIFS_SessSetup(unsigned int xid, struct cifsSesInfo *ses, int first_time,
const struct nls_table *nls_cp)
@@ -430,6 +576,7 @@ CIFS_SessSetup(unsigned int xid, struct cifsSesInfo *ses, int first_time,
__u16 action;
int bytes_remaining;
struct key *spnego_key = NULL;
+ __le32 phase = NtLmNegotiate; /* NTLMSSP, if needed, is multistage */
if (ses == NULL)
return -EINVAL;
@@ -437,6 +584,10 @@ CIFS_SessSetup(unsigned int xid, struct cifsSesInfo *ses, int first_time,
type = ses->server->secType;
cFYI(1, ("sess setup type %d", type));
+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.
@@ -650,9 +801,53 @@ CIFS_SessSetup(unsigned int xid, struct cifsSesInfo *ses, int first_time,
goto ssetup_exit;
#endif /* CONFIG_CIFS_UPCALL */
} else {
+#ifdef CONFIG_CIFS_EXPERIMENTAL
+ if ((experimEnabled > 1) && (type == RawNTLMSSP)) {
+ if ((pSMB->req.hdr.Flags2 & SMBFLG2_UNICODE) == 0) {
+ cERROR(1, ("NTLMSSP requires Unicode support"));
+ rc = -ENOSYS;
+ goto ssetup_exit;
+ }
+
+ cFYI(1, ("ntlmssp session setup phase %d", phase));
+ pSMB->req.hdr.Flags2 |= SMBFLG2_EXT_SEC;
+ capabilities |= CAP_EXTENDED_SECURITY;
+ pSMB->req.Capabilities |= cpu_to_le32(capabilities);
+ if (phase == NtLmNegotiate) {
+ setup_ntlmssp_neg_req(pSMB, ses);
+ iov[1].iov_len = sizeof(NEGOTIATE_MESSAGE);
+ } else if (phase == NtLmAuthenticate) {
+ int blob_len;
+ blob_len = setup_ntlmssp_auth_req(pSMB, ses,
+ nls_cp,
+ first_time);
+ iov[1].iov_len = 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;
+ } else {
+ cERROR(1, ("invalid phase %d", phase));
+ rc = -ENOSYS;
+ goto ssetup_exit;
+ }
+ iov[1].iov_base = &pSMB->req.SecurityBlob[0];
+ /* 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 {
+ cERROR(1, ("secType %d not supported!", type));
+ rc = -ENOSYS;
+ goto ssetup_exit;
+ }
+#else
cERROR(1, ("secType %d not supported!", type));
rc = -ENOSYS;
goto ssetup_exit;
+#endif
}
iov[2].iov_base = str_area;
@@ -668,12 +863,23 @@ CIFS_SessSetup(unsigned int xid, struct cifsSesInfo *ses, int first_time,
/* SMB request buf freed in SendReceive2 */
cFYI(1, ("ssetup rc from sendrecv2 is %d", rc));
- if (rc)
- goto ssetup_exit;
pSMB = (SESSION_SETUP_ANDX *)iov[0].iov_base;
smb_buf = (struct smb_hdr *)iov[0].iov_base;
+ if ((type == RawNTLMSSP) && (smb_buf->Status.CifsError ==
+ cpu_to_le32(NT_STATUS_MORE_PROCESSING_REQUIRED))) {
+ if (phase != NtLmNegotiate) {
+ cERROR(1, ("Unexpected more processing error"));
+ 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 */
+ }
+ if (rc)
+ goto ssetup_exit;
+
if ((smb_buf->WordCount != 3) && (smb_buf->WordCount != 4)) {
rc = -EIO;
cERROR(1, ("bad word count %d", smb_buf->WordCount));
@@ -692,22 +898,33 @@ CIFS_SessSetup(unsigned int xid, struct cifsSesInfo *ses, int first_time,
if (smb_buf->WordCount == 4) {
__u16 blob_len;
blob_len = le16_to_cpu(pSMB->resp.SecurityBlobLength);
- bcc_ptr += blob_len;
if (blob_len > bytes_remaining) {
cERROR(1, ("bad security blob length %d", blob_len));
rc = -EINVAL;
goto ssetup_exit;
}
+ if (phase == NtLmChallenge) {
+ rc = decode_ntlmssp_challenge(bcc_ptr, blob_len, ses);
+ /* now goto beginning for ntlmssp authenticate phase */
+ if (rc)
+ goto ssetup_exit;
+ }
+ bcc_ptr += blob_len;
bytes_remaining -= blob_len;
}
/* BB check if Unicode and decode strings */
- if (smb_buf->Flags2 & SMBFLG2_UNICODE)
- rc = decode_unicode_ssetup(&bcc_ptr, bytes_remaining,
- ses, nls_cp);
- 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, nls_cp);
+ } else {
rc = decode_ascii_ssetup(&bcc_ptr, bytes_remaining,
ses, nls_cp);
+ }
ssetup_exit:
if (spnego_key) {
@@ -721,5 +938,9 @@ ssetup_exit:
} 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;
+
return rc;
}