diff options
Diffstat (limited to 'fs/cifs/sess.c')
-rw-r--r-- | fs/cifs/sess.c | 373 |
1 files changed, 297 insertions, 76 deletions
diff --git a/fs/cifs/sess.c b/fs/cifs/sess.c index 5c68b42..897a052 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; } |