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 5c68b4282be..897a052270f 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;  }  |